diff --git a/.gitignore b/.gitignore index b966fa1..3f09839 100644 --- a/.gitignore +++ b/.gitignore @@ -297,4 +297,5 @@ __pycache__/ *.xsd.cs *.bak -package-lock.json \ No newline at end of file +package-lock.json +*.jwk diff --git a/AspNetCore.Reporting.Angular/Areas/Identity/IdentityHostingStartup.cs b/AspNetCore.Reporting.Angular/Areas/Identity/IdentityHostingStartup.cs deleted file mode 100644 index ba48bdd..0000000 --- a/AspNetCore.Reporting.Angular/Areas/Identity/IdentityHostingStartup.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Hosting; - -[assembly: HostingStartup(typeof(AspNetCore.Reporting.Angular.Areas.Identity.IdentityHostingStartup))] -namespace AspNetCore.Reporting.Angular.Areas.Identity -{ - public class IdentityHostingStartup : IHostingStartup - { - public void Configure(IWebHostBuilder builder) - { - builder.ConfigureServices((context, services) => { - }); - } - } -} diff --git a/AspNetCore.Reporting.Angular/ClientApp/.browserslistrc b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.browserslistrc similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/.browserslistrc rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.browserslistrc index b44a58d..427441d 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/.browserslistrc +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.browserslistrc @@ -1,17 +1,17 @@ -# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. -# For additional information regarding the format and rule options, please see: -# https://github.com/browserslist/browserslist#queries - -# For the full list of supported browsers by the Angular framework, please see: -# https://angular.io/guide/browser-support - -# You can see what browsers were selected by your queries by running: -# npx browserslist - -last 1 Chrome version -last 1 Firefox version -last 2 Edge major versions -last 2 Safari major versions -last 2 iOS major versions -Firefox ESR -not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR +not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. diff --git a/AspNetCore.Reporting.Angular/ClientApp/.editorconfig b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.editorconfig similarity index 94% rename from AspNetCore.Reporting.Angular/ClientApp/.editorconfig rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.editorconfig index f6f117c..6e87a00 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/.editorconfig +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.editorconfig @@ -1,13 +1,13 @@ -# Editor configuration, see http://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.md] -max_line_length = off -trim_trailing_whitespace = false +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/AspNetCore.Reporting.Angular/ClientApp/.gitignore b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.gitignore similarity index 92% rename from AspNetCore.Reporting.Angular/ClientApp/.gitignore rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.gitignore index 8ff57b1..f223182 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/.gitignore +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/.gitignore @@ -1,41 +1,41 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist -/dist-server -/tmp -/.angular -/out-tsc - -# dependencies -/node_modules - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -# misc -/.sass-cache -/connect.lock -/coverage -/libpeerconnection.log -npm-debug.log -yarn-error.log -testem.log -/typings - -# System Files -.DS_Store -Thumbs.db +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/dist-server +/tmp +/.angular +/out-tsc + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/AspNetCore.Reporting.Angular.Client.esproj b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/AspNetCore.Reporting.Angular.Client.esproj new file mode 100644 index 0000000..82ee5e4 --- /dev/null +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/AspNetCore.Reporting.Angular.Client.esproj @@ -0,0 +1,13 @@ + + + npm start + + false + + $(MSBuildProjectDirectory)\dist\AspNetCore.Reporting.Angular.Client\browser\ + + + + + + \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ClientApp/README.md b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/README.md similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/README.md rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/README.md index b212424..0fc25a9 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/README.md +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/README.md @@ -1,27 +1,27 @@ -# AspNetCore.Reporting.Angular - -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0. - -## Development server - -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). +# AspNetCore.Reporting.Angular + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/AspNetCore.Reporting.Angular/ClientApp/angular.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/angular.json similarity index 56% rename from AspNetCore.Reporting.Angular/ClientApp/angular.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/angular.json index 804e89a..1489fba 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/angular.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/angular.json @@ -1,123 +1,104 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "AspNetCore.Reporting.Angular": { - "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", - "tsConfig": "tsconfig.app.json", - "assets": [ - "src/assets" - ], - "styles": [ - "node_modules/bootstrap/dist/css/bootstrap.min.css", - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "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": { - "browserTarget": "AspNetCore.Reporting.Angular:build:production" - }, - "development": { - "browserTarget": "AspNetCore.Reporting.Angular:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "AspNetCore.Reporting.Angular:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - } - }, - "server": { - "builder": "@angular-devkit/build-angular:server", - "options": { - "outputPath": "dist-server", - "main": "src/main.ts", - "tsConfig": "tsconfig.server.json" - }, - "configurations": { - "dev": { - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": true - }, - "production": { - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false - } - } - } - } - } - }, - "defaultProject": "AspNetCore.Reporting.Angular", - "cli": { - "analytics": false - } -} +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "AspNetCore.Reporting.Angular.Client": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "standalone": false + }, + "@schematics/angular:directive": { + "standalone": false + }, + "@schematics/angular:pipe": { + "standalone": false + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/AspNetCore.Reporting.Angular.Client", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public", + "output": "public/assets" + } + ], + "styles": [ + "node_modules/bootstrap/dist/css/bootstrap.min.css", + "src/styles.css" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "AspNetCore.Reporting.Angular.Client:build:production" + }, + "development": { + "buildTarget": "AspNetCore.Reporting.Angular.Client:build:development" + } + }, + "defaultConfiguration": "development", + "options": { + "proxyConfig": "src/proxy.conf.js" + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n" + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "assets": [ + "src/assets" + ], + "styles": [ + "src/styles.css" + ], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": false + } +} diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/aspnetcore-https.js b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/aspnetcore-https.js new file mode 100644 index 0000000..5665e3f --- /dev/null +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/aspnetcore-https.js @@ -0,0 +1,37 @@ +// 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=(?.+)/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=<> explicitly.') + process.exit(-1); +} + +const certFilePath = path.join(baseFolder, `${certificateName}.pem`); +const keyFilePath = path.join(baseFolder, `${certificateName}.key`); + +if(!fs.existsSync(baseFolder)) { + fs.mkdirSync(baseFolder, { recursive: true }); +} + +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)); +} \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ClientApp/e2e/protractor.conf.js b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/protractor.conf.js similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/e2e/protractor.conf.js rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/protractor.conf.js index 6639854..d60eff0 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/e2e/protractor.conf.js +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/protractor.conf.js @@ -1,28 +1,28 @@ -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require("jasmine-spec-reporter"); - -exports.config = { - allScriptsTimeout: 11000, - specs: ["./src/**/*.e2e-spec.ts"], - capabilities: { - browserName: "chrome" - }, - directConnect: true, - baseUrl: "http://localhost:4200/", - framework: "jasmine", - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require("ts-node").register({ - project: require("path").join(__dirname, "./tsconfig.e2e.json") - }); - jasmine - .getEnv() - .addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require("jasmine-spec-reporter"); + +exports.config = { + allScriptsTimeout: 11000, + specs: ["./src/**/*.e2e-spec.ts"], + capabilities: { + browserName: "chrome" + }, + directConnect: true, + baseUrl: "http://localhost:4200/", + framework: "jasmine", + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require("ts-node").register({ + project: require("path").join(__dirname, "./tsconfig.e2e.json") + }); + jasmine + .getEnv() + .addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/AspNetCore.Reporting.Angular/ClientApp/e2e/src/app.e2e-spec.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/src/app.e2e-spec.ts similarity index 95% rename from AspNetCore.Reporting.Angular/ClientApp/e2e/src/app.e2e-spec.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/src/app.e2e-spec.ts index dc99225..5b3b4b2 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/e2e/src/app.e2e-spec.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/src/app.e2e-spec.ts @@ -1,14 +1,14 @@ -import { AppPage } from './app.po'; - -describe('App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display welcome message', () => { - page.navigateTo(); - expect(page.getMainHeading()).toEqual('Hello, world!'); - }); -}); +import { AppPage } from './app.po'; + +describe('App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getMainHeading()).toEqual('Hello, world!'); + }); +}); diff --git a/AspNetCore.Reporting.Angular/ClientApp/e2e/src/app.po.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/src/app.po.ts similarity index 94% rename from AspNetCore.Reporting.Angular/ClientApp/e2e/src/app.po.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/src/app.po.ts index 09a89fa..24bc8b3 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/e2e/src/app.po.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/src/app.po.ts @@ -1,11 +1,11 @@ -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo() { - return browser.get('/'); - } - - getMainHeading() { - return element(by.css('app-root h1')).getText(); - } -} +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get('/'); + } + + getMainHeading() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/e2e/tsconfig.e2e.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/tsconfig.e2e.json similarity index 94% rename from AspNetCore.Reporting.Angular/ClientApp/e2e/tsconfig.e2e.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/tsconfig.e2e.json index 43d5b30..a6dd622 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/e2e/tsconfig.e2e.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/e2e/tsconfig.e2e.json @@ -1,13 +1,13 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/app", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "jasminewd2", - "node" - ] - } +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } } \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ClientApp/package.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/package.json similarity index 75% rename from AspNetCore.Reporting.Angular/ClientApp/package.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/package.json index b021205..eb0cf0e 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/package.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/package.json @@ -1,56 +1,59 @@ -{ - "name": "aspnetcore.reporting.angular", - "version": "0.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build", - "build:ssr": "ng run aspnetcore.reporting.angular:server:dev", - "watch": "ng build --watch --configuration development", - "test": "ng test" - }, - "private": true, - "dependencies": { - "@angular/animations": "^17.0.0", - "@angular/common": "^17.0.0", - "@angular/compiler": "^17.0.0", - "@angular/core": "^17.0.0", - "@angular/forms": "^17.0.0", - "@angular/platform-browser": "^17.0.0", - "@angular/platform-browser-dynamic": "^17.0.0", - "@angular/platform-server": "^17.0.0", - "@angular/router": "^17.0.0", - "devextreme": "24.2-stable", - "devextreme-angular": "24.2-stable", - "@devexpress/analytics-core": "24.2-stable", - "devexpress-reporting-angular": "24.2-stable", - "devexpress-richedit": "24.2-stable", - "bootstrap": "^5.1.3", - "jquery": "^3.6.0", - "oidc-client": "^1.11.5", - "popper.js": "^1.16.0", - "run-script-os": "^1.1.6", - "rxjs": "~7.8.0", - "tslib": "^2.3.0", - "zone.js": "~0.14.2" - }, - "devDependencies": { - "@angular-devkit/build-angular": "^17.0.0", - "@angular/cli": "^17.0.0", - "@angular/compiler-cli": "^17.0.0", - "@angular/language-service": "^17.0.0", - "@types/jasmine": "~5.1.0", - "@types/node": "^18.11.9", - "jasmine-core": "~5.1.0", - "karma": "~6.4.0", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.0", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "~5.2.2" - }, - "overrides": { - "autoprefixer": "10.4.5" - }, - "optionalDependencies": {} -} +{ + "name": "aspnetcore.reporting.angular.client", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "run-script-os", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "prestart": "node aspnetcore-https", + "start:windows": "ng serve --ssl --ssl-cert \"%APPDATA%\\ASP.NET\\https\\%npm_package_name%.pem\" --ssl-key \"%APPDATA%\\ASP.NET\\https\\%npm_package_name%.key\" --host=localhost", + "start:default": "ng serve --ssl --ssl-cert \"$HOME/.aspnet/https/${npm_package_name}.pem\" --ssl-key \"$HOME/.aspnet/https/${npm_package_name}.key\" --host=localhost", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^17.0.0", + "@angular/common": "^17.0.0", + "@angular/compiler": "^17.0.0", + "@angular/core": "^17.0.0", + "@angular/forms": "^17.0.0", + "@angular/platform-browser": "^17.0.0", + "@angular/platform-browser-dynamic": "^17.0.0", + "@angular/platform-server": "^17.0.0", + "@angular/router": "^17.0.0", + "devextreme": "24.2-stable", + "devextreme-angular": "24.2-stable", + "@devexpress/analytics-core": "24.2-stable", + "devexpress-reporting-angular": "24.2-stable", + "devexpress-richedit": "24.2-stable", + "run-script-os": "*", + "bootstrap": "^5.1.3", + "jquery": "^3.6.0", + "oidc-client": "^1.11.5", + "popper.js": "^1.16.0", + "run-script-os": "^1.1.6", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.14.2" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^17.0.0", + "@angular/cli": "^17.0.0", + "@angular/compiler-cli": "^17.0.0", + "@angular/language-service": "^17.0.0", + "@types/jasmine": "~5.1.0", + "@types/node": "^18.11.9", + "jasmine-core": "~5.1.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "~5.2.2" + }, + "overrides": { + "autoprefixer": "10.4.5" + }, + "optionalDependencies": {} +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.constants.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.constants.ts similarity index 92% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.constants.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.constants.ts index 43b7d17..0e77c5a 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.constants.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.constants.ts @@ -1,81 +1,83 @@ -export const ApplicationName = 'AspNetCore.Reporting.Angular'; - -export const ReturnUrlType = 'returnUrl'; - -export const QueryParameterNames = { - ReturnUrl: ReturnUrlType, - Message: 'message' -}; - -export const LogoutActions = { - LogoutCallback: 'logout-callback', - Logout: 'logout', - LoggedOut: 'logged-out' -}; - -export const LoginActions = { - Login: 'login', - LoginCallback: 'login-callback', - LoginFailed: 'login-failed', - Profile: 'profile', - Register: 'register' -}; - -let applicationPaths: ApplicationPathsType = { - DefaultLoginRedirectPath: '/', - ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`, - Login: `authentication/${LoginActions.Login}`, - LoginFailed: `authentication/${LoginActions.LoginFailed}`, - LoginCallback: `authentication/${LoginActions.LoginCallback}`, - Register: `authentication/${LoginActions.Register}`, - Profile: `authentication/${LoginActions.Profile}`, - LogOut: `authentication/${LogoutActions.Logout}`, - LoggedOut: `authentication/${LogoutActions.LoggedOut}`, - LogOutCallback: `authentication/${LogoutActions.LogoutCallback}`, - LoginPathComponents: [], - LoginFailedPathComponents: [], - LoginCallbackPathComponents: [], - RegisterPathComponents: [], - ProfilePathComponents: [], - LogOutPathComponents: [], - LoggedOutPathComponents: [], - LogOutCallbackPathComponents: [], - IdentityRegisterPath: '/Identity/Account/Register', - IdentityManagePath: '/Identity/Account/Manage' -}; - -applicationPaths = { - ...applicationPaths, - LoginPathComponents: applicationPaths.Login.split('/'), - LoginFailedPathComponents: applicationPaths.LoginFailed.split('/'), - RegisterPathComponents: applicationPaths.Register.split('/'), - ProfilePathComponents: applicationPaths.Profile.split('/'), - LogOutPathComponents: applicationPaths.LogOut.split('/'), - LoggedOutPathComponents: applicationPaths.LoggedOut.split('/'), - LogOutCallbackPathComponents: applicationPaths.LogOutCallback.split('/') -}; - -interface ApplicationPathsType { - readonly DefaultLoginRedirectPath: string; - readonly ApiAuthorizationClientConfigurationUrl: string; - readonly Login: string; - readonly LoginFailed: string; - readonly LoginCallback: string; - readonly Register: string; - readonly Profile: string; - readonly LogOut: string; - readonly LoggedOut: string; - readonly LogOutCallback: string; - readonly LoginPathComponents: string []; - readonly LoginFailedPathComponents: string []; - readonly LoginCallbackPathComponents: string []; - readonly RegisterPathComponents: string []; - readonly ProfilePathComponents: string []; - readonly LogOutPathComponents: string []; - readonly LoggedOutPathComponents: string []; - readonly LogOutCallbackPathComponents: string []; - readonly IdentityRegisterPath: string; - readonly IdentityManagePath: string; -} - -export const ApplicationPaths: ApplicationPathsType = applicationPaths; +import { environment } from "../environments/environment"; + +export const ApplicationName = 'AspNetCore.Reporting.Angular.Client'; + +export const ReturnUrlType = 'returnUrl'; + +export const QueryParameterNames = { + ReturnUrl: ReturnUrlType, + Message: 'message' +}; + +export const LogoutActions = { + LogoutCallback: 'logout-callback', + Logout: 'logout', + LoggedOut: 'logged-out' +}; + +export const LoginActions = { + Login: 'login', + LoginCallback: 'login-callback', + LoginFailed: 'login-failed', + Profile: 'profile', + Register: 'register' +}; + +let applicationPaths: ApplicationPathsType = { + DefaultLoginRedirectPath: '/', + ApiAuthorizationClientConfigurationUrl: `${environment.serverUri}_configuration/${ApplicationName}`, + Login: `authentication/${LoginActions.Login}`, + LoginFailed: `authentication/${LoginActions.LoginFailed}`, + LoginCallback: `authentication/${LoginActions.LoginCallback}`, + Register: `authentication/${LoginActions.Register}`, + Profile: `authentication/${LoginActions.Profile}`, + LogOut: `authentication/${LogoutActions.Logout}`, + LoggedOut: `authentication/${LogoutActions.LoggedOut}`, + LogOutCallback: `authentication/${LogoutActions.LogoutCallback}`, + LoginPathComponents: [], + LoginFailedPathComponents: [], + LoginCallbackPathComponents: [], + RegisterPathComponents: [], + ProfilePathComponents: [], + LogOutPathComponents: [], + LoggedOutPathComponents: [], + LogOutCallbackPathComponents: [], + IdentityRegisterPath: '/Identity/Account/Register', + IdentityManagePath: '/Identity/Account/Manage' +}; + +applicationPaths = { + ...applicationPaths, + LoginPathComponents: applicationPaths.Login.split('/'), + LoginFailedPathComponents: applicationPaths.LoginFailed.split('/'), + RegisterPathComponents: applicationPaths.Register.split('/'), + ProfilePathComponents: applicationPaths.Profile.split('/'), + LogOutPathComponents: applicationPaths.LogOut.split('/'), + LoggedOutPathComponents: applicationPaths.LoggedOut.split('/'), + LogOutCallbackPathComponents: applicationPaths.LogOutCallback.split('/') +}; + +interface ApplicationPathsType { + readonly DefaultLoginRedirectPath: string; + readonly ApiAuthorizationClientConfigurationUrl: string; + readonly Login: string; + readonly LoginFailed: string; + readonly LoginCallback: string; + readonly Register: string; + readonly Profile: string; + readonly LogOut: string; + readonly LoggedOut: string; + readonly LogOutCallback: string; + readonly LoginPathComponents: string []; + readonly LoginFailedPathComponents: string []; + readonly LoginCallbackPathComponents: string []; + readonly RegisterPathComponents: string []; + readonly ProfilePathComponents: string []; + readonly LogOutPathComponents: string []; + readonly LoggedOutPathComponents: string []; + readonly LogOutCallbackPathComponents: string []; + readonly IdentityRegisterPath: string; + readonly IdentityManagePath: string; +} + +export const ApplicationPaths: ApplicationPathsType = applicationPaths; diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.module.spec.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.module.spec.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.module.spec.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.module.spec.ts index bbbe337..f6a4b29 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.module.spec.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.module.spec.ts @@ -1,13 +1,13 @@ -import { ApiAuthorizationModule } from './api-authorization.module'; - -describe('ApiAuthorizationModule', () => { - let apiAuthorizationModule: ApiAuthorizationModule; - - beforeEach(() => { - apiAuthorizationModule = new ApiAuthorizationModule(); - }); - - it('should create an instance', () => { - expect(apiAuthorizationModule).toBeTruthy(); - }); -}); +import { ApiAuthorizationModule } from './api-authorization.module'; + +describe('ApiAuthorizationModule', () => { + let apiAuthorizationModule: ApiAuthorizationModule; + + beforeEach(() => { + apiAuthorizationModule = new ApiAuthorizationModule(); + }); + + it('should create an instance', () => { + expect(apiAuthorizationModule).toBeTruthy(); + }); +}); diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.module.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.module.ts similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.module.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.module.ts index a749566..9929b33 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/api-authorization.module.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/api-authorization.module.ts @@ -1,30 +1,30 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { LoginMenuComponent } from './login-menu/login-menu.component'; -import { LoginComponent } from './login/login.component'; -import { LogoutComponent } from './logout/logout.component'; -import { RouterModule } from '@angular/router'; -import { ApplicationPaths } from './api-authorization.constants'; -import { HttpClientModule } from '@angular/common/http'; - -@NgModule({ - imports: [ - CommonModule, - HttpClientModule, - RouterModule.forChild( - [ - { path: ApplicationPaths.Register, component: LoginComponent }, - { path: ApplicationPaths.Profile, component: LoginComponent }, - { path: ApplicationPaths.Login, component: LoginComponent }, - { path: ApplicationPaths.LoginFailed, component: LoginComponent }, - { path: ApplicationPaths.LoginCallback, component: LoginComponent }, - { path: ApplicationPaths.LogOut, component: LogoutComponent }, - { path: ApplicationPaths.LoggedOut, component: LogoutComponent }, - { path: ApplicationPaths.LogOutCallback, component: LogoutComponent } - ] - ) - ], - declarations: [LoginMenuComponent, LoginComponent, LogoutComponent], - exports: [LoginMenuComponent, LoginComponent, LogoutComponent] -}) -export class ApiAuthorizationModule { } +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { LoginMenuComponent } from './login-menu/login-menu.component'; +import { LoginComponent } from './login/login.component'; +import { LogoutComponent } from './logout/logout.component'; +import { RouterModule } from '@angular/router'; +import { ApplicationPaths } from './api-authorization.constants'; +import { HttpClientModule } from '@angular/common/http'; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + RouterModule.forChild( + [ + { path: ApplicationPaths.Register, component: LoginComponent }, + { path: ApplicationPaths.Profile, component: LoginComponent }, + { path: ApplicationPaths.Login, component: LoginComponent }, + { path: ApplicationPaths.LoginFailed, component: LoginComponent }, + { path: ApplicationPaths.LoginCallback, component: LoginComponent }, + { path: ApplicationPaths.LogOut, component: LogoutComponent }, + { path: ApplicationPaths.LoggedOut, component: LogoutComponent }, + { path: ApplicationPaths.LogOutCallback, component: LogoutComponent } + ] + ) + ], + declarations: [LoginMenuComponent, LoginComponent, LogoutComponent], + exports: [LoginMenuComponent, LoginComponent, LogoutComponent] +}) +export class ApiAuthorizationModule { } diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.guard.spec.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.guard.spec.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.guard.spec.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.guard.spec.ts index 9290981..4a4c383 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.guard.spec.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.guard.spec.ts @@ -1,15 +1,15 @@ -import { TestBed, inject } from '@angular/core/testing'; - -import { AuthorizeGuard } from './authorize.guard'; - -describe('AuthorizeGuard', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [AuthorizeGuard] - }); - }); - - it('should ...', inject([AuthorizeGuard], (guard: AuthorizeGuard) => { - expect(guard).toBeTruthy(); - })); -}); +import { TestBed, inject } from '@angular/core/testing'; + +import { AuthorizeGuard } from './authorize.guard'; + +describe('AuthorizeGuard', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthorizeGuard] + }); + }); + + it('should ...', inject([AuthorizeGuard], (guard: AuthorizeGuard) => { + expect(guard).toBeTruthy(); + })); +}); diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.guard.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.guard.ts similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.guard.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.guard.ts index 95ac6e5..690fdd9 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.guard.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.guard.ts @@ -1,30 +1,30 @@ -import { Injectable } from '@angular/core'; -import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; -import { Observable } from 'rxjs'; -import { AuthorizeService } from './authorize.service'; -import { tap } from 'rxjs/operators'; -import { ApplicationPaths, QueryParameterNames } from './api-authorization.constants'; - -@Injectable({ - providedIn: 'root' -}) -export class AuthorizeGuard implements CanActivate { - constructor(private authorize: AuthorizeService, private router: Router) { - } - canActivate( - _next: ActivatedRouteSnapshot, - state: RouterStateSnapshot): Observable | Promise | boolean { - return this.authorize.isAuthenticated() - .pipe(tap(isAuthenticated => this.handleAuthorization(isAuthenticated, state))); - } - - private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) { - if (!isAuthenticated) { - this.router.navigate(ApplicationPaths.LoginPathComponents, { - queryParams: { - [QueryParameterNames.ReturnUrl]: state.url - } - }); - } - } -} +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthorizeService } from './authorize.service'; +import { tap } from 'rxjs/operators'; +import { ApplicationPaths, QueryParameterNames } from './api-authorization.constants'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthorizeGuard implements CanActivate { + constructor(private authorize: AuthorizeService, private router: Router) { + } + canActivate( + _next: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Observable | Promise | boolean { + return this.authorize.isAuthenticated() + .pipe(tap(isAuthenticated => this.handleAuthorization(isAuthenticated, state))); + } + + private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) { + if (!isAuthenticated) { + this.router.navigate(ApplicationPaths.LoginPathComponents, { + queryParams: { + [QueryParameterNames.ReturnUrl]: state.url + } + }); + } + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.interceptor.spec.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.interceptor.spec.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.interceptor.spec.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.interceptor.spec.ts index 9ed197c..a97ac0a 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.interceptor.spec.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.interceptor.spec.ts @@ -1,15 +1,15 @@ -import { TestBed, inject } from '@angular/core/testing'; - -import { AuthorizeInterceptor } from './authorize.interceptor'; - -describe('AuthorizeInterceptor', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [AuthorizeInterceptor] - }); - }); - - it('should be created', inject([AuthorizeInterceptor], (service: AuthorizeInterceptor) => { - expect(service).toBeTruthy(); - })); -}); +import { TestBed, inject } from '@angular/core/testing'; + +import { AuthorizeInterceptor } from './authorize.interceptor'; + +describe('AuthorizeInterceptor', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthorizeInterceptor] + }); + }); + + it('should be created', inject([AuthorizeInterceptor], (service: AuthorizeInterceptor) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.interceptor.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.interceptor.ts similarity index 86% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.interceptor.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.interceptor.ts index 29241b0..7439627 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.interceptor.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.interceptor.ts @@ -1,54 +1,60 @@ -import { Injectable } from '@angular/core'; -import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { AuthorizeService } from './authorize.service'; -import { mergeMap } from 'rxjs/operators'; - -@Injectable({ - providedIn: 'root' -}) -export class AuthorizeInterceptor implements HttpInterceptor { - constructor(private authorize: AuthorizeService) { } - - intercept(req: HttpRequest, next: HttpHandler): Observable> { - return this.authorize.getAccessToken() - .pipe(mergeMap(token => this.processRequestWithToken(token, req, next))); - } - - // Checks if there is an access_token available in the authorize service - // and adds it to the request in case it's targeted at the same origin as the - // single page application. - private processRequestWithToken(token: string | null, req: HttpRequest, next: HttpHandler) { - if (!!token && this.isSameOriginUrl(req)) { - req = req.clone({ - setHeaders: { - Authorization: `Bearer ${token}` - } - }); - } - - return next.handle(req); - } - - private isSameOriginUrl(req: any) { - // It's an absolute url with the same origin. - if (req.url.startsWith(`${window.location.origin}/`)) { - return true; - } - - // It's a protocol relative url with the same origin. - // For example: //www.example.com/api/Products - if (req.url.startsWith(`//${window.location.host}/`)) { - return true; - } - - // It's a relative url like /api/Products - if (/^\/[^\/].*/.test(req.url)) { - return true; - } - - // It's an absolute or protocol relative url that - // doesn't have the same origin. - return false; - } -} +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { AuthorizeService } from './authorize.service'; +import { mergeMap } from 'rxjs/operators'; +import { environment } from '../environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthorizeInterceptor implements HttpInterceptor { + constructor(private authorize: AuthorizeService) { } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return this.authorize.getAccessToken() + .pipe(mergeMap(token => this.processRequestWithToken(token, req, next))); + } + + // Checks if there is an access_token available in the authorize service + // and adds it to the request in case it's targeted at the same origin as the + // single page application. + private processRequestWithToken(token: string | null, req: HttpRequest, next: HttpHandler) { + if (!!token && this.isOriginValid(req)) { + req = req.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + } + + return next.handle(req); + } + + private isOriginValid(req: any) { + // It's an absolute url with the same origin. + if (req.url.startsWith(`${window.location.origin}/`)) { + return true; + } + + // It's a protocol relative url with the same origin. + // For example: //www.example.com/api/Products + if (req.url.startsWith(`//${window.location.host}/`)) { + return true; + } + + // It's an absolute server url . + if (req.url.startsWith(`${environment.serverUri}`)) { + return true; + } + + // It's a relative url like /api/Products + if (/^\/[^\/].*/.test(req.url)) { + return true; + } + + // It's an absolute or protocol relative url that + // doesn't have the same origin. + return false; + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.service.spec.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.service.spec.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.service.spec.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.service.spec.ts index bcd6e9c..41c9d65 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.service.spec.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.service.spec.ts @@ -1,15 +1,15 @@ -import { TestBed, inject } from '@angular/core/testing'; - -import { AuthorizeService } from './authorize.service'; - -describe('AuthorizeService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [AuthorizeService] - }); - }); - - it('should be created', inject([AuthorizeService], (service: AuthorizeService) => { - expect(service).toBeTruthy(); - })); -}); +import { TestBed, inject } from '@angular/core/testing'; + +import { AuthorizeService } from './authorize.service'; + +describe('AuthorizeService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthorizeService] + }); + }); + + it('should be created', inject([AuthorizeService], (service: AuthorizeService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.service.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.service.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.service.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.service.ts index 4960143..8be4b28 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/authorize.service.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/authorize.service.ts @@ -1,199 +1,198 @@ -import { Injectable } from '@angular/core'; -import { User, UserManager, WebStorageStateStore } from 'oidc-client'; -import { BehaviorSubject, concat, from, Observable } from 'rxjs'; -import { filter, map, mergeMap, take, tap } from 'rxjs/operators'; -import { ApplicationPaths, ApplicationName } from './api-authorization.constants'; - -export type IAuthenticationResult = - SuccessAuthenticationResult | - FailureAuthenticationResult | - RedirectAuthenticationResult; - -export interface SuccessAuthenticationResult { - status: AuthenticationResultStatus.Success; - state: any; -} - -export interface FailureAuthenticationResult { - status: AuthenticationResultStatus.Fail; - message: string; -} - -export interface RedirectAuthenticationResult { - status: AuthenticationResultStatus.Redirect; -} - -export enum AuthenticationResultStatus { - Success, - Redirect, - Fail -} - -export interface IUser { - name?: string; -} - -@Injectable({ - providedIn: 'root' -}) -export class AuthorizeService { - // By default pop ups are disabled because they don't work properly on Edge. - // If you want to enable pop up authentication simply set this flag to false. - - private popUpDisabled = true; - private userManager!: UserManager; - private userSubject = new BehaviorSubject(null); - - public isAuthenticated(): Observable { - return this.getUser().pipe(map(u => !!u)); - } - - public getUser(): Observable { - return concat( - this.userSubject.pipe(take(1), filter(u => !!u)), - this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))), - this.userSubject.asObservable()); - } - - public getAccessToken(): Observable { - return from(this.ensureUserManagerInitialized()) - .pipe(mergeMap(() => from(this.userManager.getUser())), - map(user => user && user.access_token)); - } - - // We try to authenticate the user in three different ways: - // 1) We try to see if we can authenticate the user silently. This happens - // when the user is already logged in on the IdP and is done using a hidden iframe - // on the client. - // 2) We try to authenticate the user using a PopUp Window. This might fail if there is a - // Pop-Up blocker or the user has disabled PopUps. - // 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional - // redirect flow. - public async signIn(state: any): Promise { - await this.ensureUserManagerInitialized(); - let user: User | null = null; - try { - user = await this.userManager.signinSilent(this.createArguments()); - this.userSubject.next(user.profile); - return this.success(state); - } catch (silentError) { - // User might not be authenticated, fallback to popup authentication - console.log('Silent authentication error: ', silentError); - - try { - if (this.popUpDisabled) { - throw new Error('Popup disabled. Change \'authorize.service.ts:AuthorizeService.popupDisabled\' to false to enable it.'); - } - user = await this.userManager.signinPopup(this.createArguments()); - this.userSubject.next(user.profile); - return this.success(state); - } catch (popupError: any) { - if (popupError.message === 'Popup window closed') { - // The user explicitly cancelled the login action by closing an opened popup. - return this.error('The user closed the window.'); - } else if (!this.popUpDisabled) { - console.log('Popup authentication error: ', popupError); - } - - // PopUps might be blocked by the user, fallback to redirect - try { - await this.userManager.signinRedirect(this.createArguments(state)); - return this.redirect(); - } catch (redirectError) { - console.log('Redirect authentication error: ', redirectError); - return this.error(`${redirectError}`); - } - } - } - } - - public async completeSignIn(url: string): Promise { - try { - await this.ensureUserManagerInitialized(); - const user = await this.userManager.signinCallback(url); - this.userSubject.next(user && user.profile); - return this.success(user && user.state); - } catch (error) { - console.log('There was an error signing in: ', error); - return this.error('There was an error signing in.'); - } - } - - public async signOut(state: any): Promise { - try { - if (this.popUpDisabled) { - throw new Error('Popup disabled. Change \'authorize.service.ts:AuthorizeService.popupDisabled\' to false to enable it.'); - } - - await this.ensureUserManagerInitialized(); - await this.userManager.signoutPopup(this.createArguments()); - this.userSubject.next(null); - return this.success(state); - } catch (popupSignOutError) { - console.log('Popup signout error: ', popupSignOutError); - try { - await this.userManager.signoutRedirect(this.createArguments(state)); - return this.redirect(); - } catch (redirectSignOutError) { - console.log('Redirect signout error: ', popupSignOutError); - return this.error(`${redirectSignOutError}`); - } - } - } - - public async completeSignOut(url: string): Promise { - await this.ensureUserManagerInitialized(); - try { - const response = await this.userManager.signoutCallback(url); - this.userSubject.next(null); - return this.success(response && response.state); - } catch (error) { - console.log(`There was an error trying to log out '${error}'.`); - return this.error(`${error}`); - } - } - - private createArguments(state?: any): any { - return { useReplaceToNavigate: true, data: state }; - } - - private error(message: string): IAuthenticationResult { - return { status: AuthenticationResultStatus.Fail, message }; - } - - private success(state: any): IAuthenticationResult { - return { status: AuthenticationResultStatus.Success, state }; - } - - private redirect(): IAuthenticationResult { - return { status: AuthenticationResultStatus.Redirect }; - } - - private async ensureUserManagerInitialized(): Promise { - if (this.userManager !== undefined) { - return; - } - - const response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl); - if (!response.ok) { - throw new Error(`Could not load settings for '${ApplicationName}'`); - } - - const settings: any = await response.json(); - settings.automaticSilentRenew = true; - settings.includeIdTokenInSilentRenew = true; - this.userManager = new UserManager(settings); - - this.userManager.events.addUserSignedOut(async () => { - await this.userManager.removeUser(); - this.userSubject.next(null); - }); - } - - private getUserFromStorage(): Observable { - return from(this.ensureUserManagerInitialized()) - .pipe( - mergeMap(() => this.userManager.getUser()), - map(u => u && u.profile)); - } -} +import { Injectable } from '@angular/core'; +import { User, UserManager} from 'oidc-client'; +import { BehaviorSubject, concat, from, Observable } from 'rxjs'; +import { filter, map, mergeMap, take, tap } from 'rxjs/operators'; +import { ApplicationPaths, ApplicationName } from './api-authorization.constants'; + +export type IAuthenticationResult = + SuccessAuthenticationResult | + FailureAuthenticationResult | + RedirectAuthenticationResult; + +export interface SuccessAuthenticationResult { + status: AuthenticationResultStatus.Success; + state: any; +} + +export interface FailureAuthenticationResult { + status: AuthenticationResultStatus.Fail; + message: string; +} + +export interface RedirectAuthenticationResult { + status: AuthenticationResultStatus.Redirect; +} + +export enum AuthenticationResultStatus { + Success, + Redirect, + Fail +} + +export interface IUser { + name?: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthorizeService { + // By default pop ups are disabled because they don't work properly on Edge. + // If you want to enable pop up authentication simply set this flag to false. + private popUpDisabled = true; + private userManager!: UserManager; + private userSubject = new BehaviorSubject(null); + + public isAuthenticated(): Observable { + return this.getUser().pipe(map(u => !!u)); + } + + public getUser(): Observable { + return concat( + this.userSubject.pipe(take(1), filter(u => !!u)), + this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))), + this.userSubject.asObservable()); + } + + public getAccessToken(): Observable { + return from(this.ensureUserManagerInitialized()) + .pipe(mergeMap(() => from(this.userManager.getUser())), + map(user => user && user.access_token)); + } + + // We try to authenticate the user in three different ways: + // 1) We try to see if we can authenticate the user silently. This happens + // when the user is already logged in on the IdP and is done using a hidden iframe + // on the client. + // 2) We try to authenticate the user using a PopUp Window. This might fail if there is a + // Pop-Up blocker or the user has disabled PopUps. + // 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional + // redirect flow. + public async signIn(state: any): Promise { + await this.ensureUserManagerInitialized(); + let user: User | null = null; + try { + user = await this.userManager.signinSilent(this.createArguments()); + this.userSubject.next(user.profile); + return this.success(state); + } catch (silentError) { + // User might not be authenticated, fallback to popup authentication + console.log('Silent authentication error: ', silentError); + + try { + if (this.popUpDisabled) { + throw new Error('Popup disabled. Change \'authorize.service.ts:AuthorizeService.popupDisabled\' to false to enable it.'); + } + user = await this.userManager.signinPopup(this.createArguments()); + this.userSubject.next(user.profile); + return this.success(state); + } catch (popupError: any) { + if (popupError.message === 'Popup window closed') { + // The user explicitly cancelled the login action by closing an opened popup. + return this.error('The user closed the window.'); + } else if (!this.popUpDisabled) { + console.log('Popup authentication error: ', popupError); + } + + // PopUps might be blocked by the user, fallback to redirect + try { + await this.userManager.signinRedirect(this.createArguments(state)); + return this.redirect(); + } catch (redirectError) { + console.log('Redirect authentication error: ', redirectError); + return this.error(`${redirectError}`); + } + } + } + } + + public async completeSignIn(url: string): Promise { + try { + await this.ensureUserManagerInitialized(); + const user = await this.userManager.signinCallback(url); + this.userSubject.next(user && user.profile); + return this.success(user && user.state); + } catch (error) { + console.log('There was an error signing in: ', error); + return this.error('There was an error signing in.'); + } + } + + public async signOut(state: any): Promise { + try { + if (this.popUpDisabled) { + throw new Error('Popup disabled. Change \'authorize.service.ts:AuthorizeService.popupDisabled\' to false to enable it.'); + } + + await this.ensureUserManagerInitialized(); + await this.userManager.signoutPopup(this.createArguments()); + this.userSubject.next(null); + return this.success(state); + } catch (popupSignOutError) { + console.log('Popup signout error: ', popupSignOutError); + try { + await this.userManager.signoutRedirect(this.createArguments(state)); + return this.redirect(); + } catch (redirectSignOutError) { + console.log('Redirect signout error: ', popupSignOutError); + return this.error(`${redirectSignOutError}`); + } + } + } + + public async completeSignOut(url: string): Promise { + await this.ensureUserManagerInitialized(); + try { + const response = await this.userManager.signoutCallback(url); + this.userSubject.next(null); + return this.success(response && response.state); + } catch (error) { + console.log(`There was an error trying to log out '${error}'.`); + return this.error(`${error}`); + } + } + + private createArguments(state?: any): any { + return { useReplaceToNavigate: true, data: state }; + } + + private error(message: string): IAuthenticationResult { + return { status: AuthenticationResultStatus.Fail, message }; + } + + private success(state: any): IAuthenticationResult { + return { status: AuthenticationResultStatus.Success, state }; + } + + private redirect(): IAuthenticationResult { + return { status: AuthenticationResultStatus.Redirect }; + } + + private async ensureUserManagerInitialized(): Promise { + if (this.userManager !== undefined) { + return; + } + + const response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl); + if (!response.ok) { + throw new Error(`Could not load settings for '${ApplicationName}'`); + } + + const settings: any = await response.json(); + settings.automaticSilentRenew = true; + settings.includeIdTokenInSilentRenew = true; + this.userManager = new UserManager(settings); + + this.userManager.events.addUserSignedOut(async () => { + await this.userManager.removeUser(); + this.userSubject.next(null); + }); + } + + private getUserFromStorage(): Observable { + return from(this.ensureUserManagerInitialized()) + .pipe( + mergeMap(() => this.userManager.getUser()), + map(u => u && u.profile)); + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.css b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.css similarity index 100% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.css rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.css diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.html similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.html index f43ba6a..30fa656 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.html +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.html @@ -1,16 +1,16 @@ - - + + diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.spec.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.spec.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.spec.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.spec.ts index 00ba8a1..3c8c180 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.spec.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.spec.ts @@ -1,25 +1,25 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LoginMenuComponent } from './login-menu.component'; - -describe('LoginMenuComponent', () => { - let component: LoginMenuComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ LoginMenuComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LoginMenuComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginMenuComponent } from './login-menu.component'; + +describe('LoginMenuComponent', () => { + let component: LoginMenuComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LoginMenuComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.ts similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.ts index eb69dcd..fb15863 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login-menu/login-menu.component.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login-menu/login-menu.component.ts @@ -1,21 +1,21 @@ -import { Component, OnInit } from '@angular/core'; -import { AuthorizeService } from '../authorize.service'; -import { Observable } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; - -@Component({ - selector: 'app-login-menu', - templateUrl: './login-menu.component.html', - styleUrls: ['./login-menu.component.css'] -}) -export class LoginMenuComponent implements OnInit { - public isAuthenticated!: Observable; - public userName!: Observable; - - constructor(private authorizeService: AuthorizeService) { } - - ngOnInit() { - this.isAuthenticated = this.authorizeService.isAuthenticated(); - this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name)); - } -} +import { Component, OnInit } from '@angular/core'; +import { AuthorizeService } from '../authorize.service'; +import { Observable } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; + +@Component({ + selector: 'app-login-menu', + templateUrl: './login-menu.component.html', + styleUrls: ['./login-menu.component.css'] +}) +export class LoginMenuComponent implements OnInit { + public isAuthenticated!: Observable; + public userName!: Observable; + + constructor(private authorizeService: AuthorizeService) { } + + ngOnInit() { + this.isAuthenticated = this.authorizeService.isAuthenticated(); + this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name)); + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.css b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.css similarity index 100% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.css rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.css diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.html similarity index 100% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.html diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.spec.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.spec.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.spec.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.spec.ts index 759cc33..d6d85a8 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.spec.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.spec.ts @@ -1,25 +1,25 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LoginComponent } from './login.component'; - -describe('LoginComponent', () => { - let component: LoginComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ LoginComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.ts similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.ts index 2e48e99..c4ced02 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/login/login.component.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/login/login.component.ts @@ -1,128 +1,128 @@ -import { Component, OnInit } from '@angular/core'; -import { AuthorizeService, AuthenticationResultStatus } from '../authorize.service'; -import { ActivatedRoute, Router } from '@angular/router'; -import { BehaviorSubject } from 'rxjs'; -import { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants'; - -// The main responsibility of this component is to handle the user's login process. -// This is the starting point for the login process. Any component that needs to authenticate -// a user can simply perform a redirect to this component with a returnUrl query parameter and -// let the component perform the login and return back to the return url. -@Component({ - selector: 'app-login', - templateUrl: './login.component.html', - styleUrls: ['./login.component.css'] -}) -export class LoginComponent implements OnInit { - public message = new BehaviorSubject(null); - - constructor( - private authorizeService: AuthorizeService, - private activatedRoute: ActivatedRoute, - private router: Router) { } - - async ngOnInit() { - const action = this.activatedRoute.snapshot.url[1]; - switch (action.path) { - case LoginActions.Login: - await this.login(this.getReturnUrl()); - break; - case LoginActions.LoginCallback: - await this.processLoginCallback(); - break; - case LoginActions.LoginFailed: - const message = this.activatedRoute.snapshot.queryParamMap.get(QueryParameterNames.Message); - this.message.next(message); - break; - case LoginActions.Profile: - this.redirectToProfile(); - break; - case LoginActions.Register: - this.redirectToRegister(); - break; - default: - throw new Error(`Invalid action '${action}'`); - } - } - - - private async login(returnUrl: string): Promise { - const state: INavigationState = { returnUrl }; - const result = await this.authorizeService.signIn(state); - this.message.next(null); - switch (result.status) { - case AuthenticationResultStatus.Redirect: - break; - case AuthenticationResultStatus.Success: - await this.navigateToReturnUrl(returnUrl); - break; - case AuthenticationResultStatus.Fail: - await this.router.navigate(ApplicationPaths.LoginFailedPathComponents, { - queryParams: { [QueryParameterNames.Message]: result.message } - }); - break; - default: - throw new Error(`Invalid status result ${(result as any).status}.`); - } - } - - private async processLoginCallback(): Promise { - const url = window.location.href; - const result = await this.authorizeService.completeSignIn(url); - switch (result.status) { - case AuthenticationResultStatus.Redirect: - // There should not be any redirects as completeSignIn never redirects. - throw new Error('Should not redirect.'); - case AuthenticationResultStatus.Success: - await this.navigateToReturnUrl(this.getReturnUrl(result.state)); - break; - case AuthenticationResultStatus.Fail: - this.message.next(result.message); - break; - } - } - - private redirectToRegister(): any { - this.redirectToApiAuthorizationPath( - `${ApplicationPaths.IdentityRegisterPath}?returnUrl=${encodeURI('/' + ApplicationPaths.Login)}`); - } - - private redirectToProfile(): void { - this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath); - } - - private async navigateToReturnUrl(returnUrl: string) { - // It's important that we do a replace here so that we remove the callback uri with the - // fragment containing the tokens from the browser history. - await this.router.navigateByUrl(returnUrl, { - replaceUrl: true - }); - } - - private getReturnUrl(state?: INavigationState): string { - const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl; - // If the url is comming from the query string, check that is either - // a relative url or an absolute url - if (fromQuery && - !(fromQuery.startsWith(`${window.location.origin}/`) || - /\/[^\/].*/.test(fromQuery))) { - // This is an extra check to prevent open redirects. - throw new Error('Invalid return url. The return url needs to have the same origin as the current page.'); - } - return (state && state.returnUrl) || - fromQuery || - ApplicationPaths.DefaultLoginRedirectPath; - } - - private redirectToApiAuthorizationPath(apiAuthorizationPath: string) { - // It's important that we do a replace here so that when the user hits the back arrow on the - // browser they get sent back to where it was on the app instead of to an endpoint on this - // component. - const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`; - window.location.replace(redirectUrl); - } -} - -interface INavigationState { - [ReturnUrlType]: string; -} +import { Component, OnInit } from '@angular/core'; +import { AuthorizeService, AuthenticationResultStatus } from '../authorize.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { BehaviorSubject } from 'rxjs'; +import { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants'; + +// The main responsibility of this component is to handle the user's login process. +// This is the starting point for the login process. Any component that needs to authenticate +// a user can simply perform a redirect to this component with a returnUrl query parameter and +// let the component perform the login and return back to the return url. +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.css'] +}) +export class LoginComponent implements OnInit { + public message = new BehaviorSubject(null); + + constructor( + private authorizeService: AuthorizeService, + private activatedRoute: ActivatedRoute, + private router: Router) { } + + async ngOnInit() { + const action = this.activatedRoute.snapshot.url[1]; + switch (action.path) { + case LoginActions.Login: + await this.login(this.getReturnUrl()); + break; + case LoginActions.LoginCallback: + await this.processLoginCallback(); + break; + case LoginActions.LoginFailed: + const message = this.activatedRoute.snapshot.queryParamMap.get(QueryParameterNames.Message); + this.message.next(message); + break; + case LoginActions.Profile: + this.redirectToProfile(); + break; + case LoginActions.Register: + this.redirectToRegister(); + break; + default: + throw new Error(`Invalid action '${action}'`); + } + } + + + private async login(returnUrl: string): Promise { + const state: INavigationState = { returnUrl }; + const result = await this.authorizeService.signIn(state); + this.message.next(null); + switch (result.status) { + case AuthenticationResultStatus.Redirect: + break; + case AuthenticationResultStatus.Success: + await this.navigateToReturnUrl(returnUrl); + break; + case AuthenticationResultStatus.Fail: + await this.router.navigate(ApplicationPaths.LoginFailedPathComponents, { + queryParams: { [QueryParameterNames.Message]: result.message } + }); + break; + default: + throw new Error(`Invalid status result ${(result as any).status}.`); + } + } + + private async processLoginCallback(): Promise { + const url = window.location.href; + const result = await this.authorizeService.completeSignIn(url); + switch (result.status) { + case AuthenticationResultStatus.Redirect: + // There should not be any redirects as completeSignIn never redirects. + throw new Error('Should not redirect.'); + case AuthenticationResultStatus.Success: + await this.navigateToReturnUrl(this.getReturnUrl(result.state)); + break; + case AuthenticationResultStatus.Fail: + this.message.next(result.message); + break; + } + } + + private redirectToRegister(): any { + this.redirectToApiAuthorizationPath( + `${ApplicationPaths.IdentityRegisterPath}?returnUrl=${encodeURI('/' + ApplicationPaths.Login)}`); + } + + private redirectToProfile(): void { + this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath); + } + + private async navigateToReturnUrl(returnUrl: string) { + // It's important that we do a replace here so that we remove the callback uri with the + // fragment containing the tokens from the browser history. + await this.router.navigateByUrl(returnUrl, { + replaceUrl: true + }); + } + + private getReturnUrl(state?: INavigationState): string { + const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl; + // If the url is comming from the query string, check that is either + // a relative url or an absolute url + if (fromQuery && + !(fromQuery.startsWith(`${window.location.origin}/`) || + /\/[^\/].*/.test(fromQuery))) { + // This is an extra check to prevent open redirects. + throw new Error('Invalid return url. The return url needs to have the same origin as the current page.'); + } + return (state && state.returnUrl) || + fromQuery || + ApplicationPaths.DefaultLoginRedirectPath; + } + + private redirectToApiAuthorizationPath(apiAuthorizationPath: string) { + // It's important that we do a replace here so that when the user hits the back arrow on the + // browser they get sent back to where it was on the app instead of to an endpoint on this + // component. + const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`; + window.location.replace(redirectUrl); + } +} + +interface INavigationState { + [ReturnUrlType]: string; +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.css b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.css similarity index 100% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.css rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.css diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.html similarity index 100% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.html diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.spec.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.spec.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.spec.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.spec.ts index 2b2e7b9..c04dbe2 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.spec.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.spec.ts @@ -1,25 +1,25 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LogoutComponent } from './logout.component'; - -describe('LogoutComponent', () => { - let component: LogoutComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ LogoutComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LogoutComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LogoutComponent } from './logout.component'; + +describe('LogoutComponent', () => { + let component: LogoutComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LogoutComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LogoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.ts similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.ts index 45155eb..a02ce64 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/api-authorization/logout/logout.component.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/api-authorization/logout/logout.component.ts @@ -1,114 +1,114 @@ -import { Component, OnInit } from '@angular/core'; -import { AuthenticationResultStatus, AuthorizeService } from '../authorize.service'; -import { BehaviorSubject } from 'rxjs'; -import { ActivatedRoute, Router } from '@angular/router'; -import { take } from 'rxjs/operators'; -import { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants'; - -// The main responsibility of this component is to handle the user's logout process. -// This is the starting point for the logout process, which is usually initiated when a -// user clicks on the logout button on the LoginMenu component. -@Component({ - selector: 'app-logout', - templateUrl: './logout.component.html', - styleUrls: ['./logout.component.css'] -}) -export class LogoutComponent implements OnInit { - public message = new BehaviorSubject(null); - - constructor( - private authorizeService: AuthorizeService, - private activatedRoute: ActivatedRoute, - private router: Router) { } - - async ngOnInit() { - const action = this.activatedRoute.snapshot.url[1]; - switch (action.path) { - case LogoutActions.Logout: - if (!!window.history.state.local) { - await this.logout(this.getReturnUrl()); - } else { - // This prevents regular links to /authentication/logout from triggering a logout - this.message.next('The logout was not initiated from within the page.'); - } - - break; - case LogoutActions.LogoutCallback: - await this.processLogoutCallback(); - break; - case LogoutActions.LoggedOut: - this.message.next('You successfully logged out!'); - break; - default: - throw new Error(`Invalid action '${action}'`); - } - } - - private async logout(returnUrl: string): Promise { - const state: INavigationState = { returnUrl }; - const isauthenticated = await this.authorizeService.isAuthenticated().pipe( - take(1) - ).toPromise(); - if (isauthenticated) { - const result = await this.authorizeService.signOut(state); - switch (result.status) { - case AuthenticationResultStatus.Redirect: - break; - case AuthenticationResultStatus.Success: - await this.navigateToReturnUrl(returnUrl); - break; - case AuthenticationResultStatus.Fail: - this.message.next(result.message); - break; - default: - throw new Error('Invalid authentication result status.'); - } - } else { - this.message.next('You successfully logged out!'); - } - } - - private async processLogoutCallback(): Promise { - const url = window.location.href; - const result = await this.authorizeService.completeSignOut(url); - switch (result.status) { - case AuthenticationResultStatus.Redirect: - // There should not be any redirects as the only time completeAuthentication finishes - // is when we are doing a redirect sign in flow. - throw new Error('Should not redirect.'); - case AuthenticationResultStatus.Success: - await this.navigateToReturnUrl(this.getReturnUrl(result.state)); - break; - case AuthenticationResultStatus.Fail: - this.message.next(result.message); - break; - default: - throw new Error('Invalid authentication result status.'); - } - } - - private async navigateToReturnUrl(returnUrl: string) { - await this.router.navigateByUrl(returnUrl, { - replaceUrl: true - }); - } - - private getReturnUrl(state?: INavigationState): string { - const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl; - // If the url is comming from the query string, check that is either - // a relative url or an absolute url - if (fromQuery && - !(fromQuery.startsWith(`${window.location.origin}/`) || - /\/[^\/].*/.test(fromQuery))) { - // This is an extra check to prevent open redirects. - throw new Error('Invalid return url. The return url needs to have the same origin as the current page.'); - } - return (state && state.returnUrl) || - fromQuery || - ApplicationPaths.LoggedOut; - } -} - -interface INavigationState { - [ReturnUrlType]: string; -} +import { Component, OnInit } from '@angular/core'; +import { AuthenticationResultStatus, AuthorizeService } from '../authorize.service'; +import { BehaviorSubject } from 'rxjs'; +import { ActivatedRoute, Router } from '@angular/router'; +import { take } from 'rxjs/operators'; +import { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants'; + +// The main responsibility of this component is to handle the user's logout process. +// This is the starting point for the logout process, which is usually initiated when a +// user clicks on the logout button on the LoginMenu component. +@Component({ + selector: 'app-logout', + templateUrl: './logout.component.html', + styleUrls: ['./logout.component.css'] +}) +export class LogoutComponent implements OnInit { + public message = new BehaviorSubject(null); + + constructor( + private authorizeService: AuthorizeService, + private activatedRoute: ActivatedRoute, + private router: Router) { } + + async ngOnInit() { + const action = this.activatedRoute.snapshot.url[1]; + switch (action.path) { + case LogoutActions.Logout: + if (!!window.history.state.local) { + await this.logout(this.getReturnUrl()); + } else { + // This prevents regular links to /authentication/logout from triggering a logout + this.message.next('The logout was not initiated from within the page.'); + } + + break; + case LogoutActions.LogoutCallback: + await this.processLogoutCallback(); + break; + case LogoutActions.LoggedOut: + this.message.next('You successfully logged out!'); + break; + default: + throw new Error(`Invalid action '${action}'`); + } + } + + private async logout(returnUrl: string): Promise { + const state: INavigationState = { returnUrl }; + const isauthenticated = await this.authorizeService.isAuthenticated().pipe( + take(1) + ).toPromise(); + if (isauthenticated) { + const result = await this.authorizeService.signOut(state); + switch (result.status) { + case AuthenticationResultStatus.Redirect: + break; + case AuthenticationResultStatus.Success: + await this.navigateToReturnUrl(returnUrl); + break; + case AuthenticationResultStatus.Fail: + this.message.next(result.message); + break; + default: + throw new Error('Invalid authentication result status.'); + } + } else { + this.message.next('You successfully logged out!'); + } + } + + private async processLogoutCallback(): Promise { + const url = window.location.href; + const result = await this.authorizeService.completeSignOut(url); + switch (result.status) { + case AuthenticationResultStatus.Redirect: + // There should not be any redirects as the only time completeAuthentication finishes + // is when we are doing a redirect sign in flow. + throw new Error('Should not redirect.'); + case AuthenticationResultStatus.Success: + await this.navigateToReturnUrl(this.getReturnUrl(result.state)); + break; + case AuthenticationResultStatus.Fail: + this.message.next(result.message); + break; + default: + throw new Error('Invalid authentication result status.'); + } + } + + private async navigateToReturnUrl(returnUrl: string) { + await this.router.navigateByUrl(returnUrl, { + replaceUrl: true + }); + } + + private getReturnUrl(state?: INavigationState): string { + const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl; + // If the url is comming from the query string, check that is either + // a relative url or an absolute url + if (fromQuery && + !(fromQuery.startsWith(`${window.location.origin}/`) || + /\/[^\/].*/.test(fromQuery))) { + // This is an extra check to prevent open redirects. + throw new Error('Invalid return url. The return url needs to have the same origin as the current page.'); + } + return (state && state.returnUrl) || + fromQuery || + ApplicationPaths.LoggedOut; + } +} + +interface INavigationState { + [ReturnUrlType]: string; +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/app.component.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.component.html similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/app.component.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.component.html index 51d4423..63beac3 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/app.component.html +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.component.html @@ -1,4 +1,4 @@ - -
- + +
+
\ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/app.component.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.component.ts similarity index 94% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/app.component.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.component.ts index 55593b3..0a40b8c 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/app.component.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.component.ts @@ -1,9 +1,9 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html' -}) -export class AppComponent { - title = 'app'; -} +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html' +}) +export class AppComponent { + title = 'app'; +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/app.module.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.module.ts similarity index 85% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/app.module.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.module.ts index 0614f76..0b7da66 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/app.module.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.module.ts @@ -1,46 +1,47 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; -import { RouterModule } from '@angular/router'; - -import { AppComponent } from './app.component'; -import { NavMenuComponent } from './nav-menu/nav-menu.component'; -import { DxReportViewerModule, DxReportDesignerModule } from 'devexpress-reporting-angular'; -import { HomeComponent } from './home/home.component'; -import { ReportListComponent } from './report-list/report.list.component'; -import { ApiAuthorizationModule } from 'src/api-authorization/api-authorization.module'; -import { AuthorizeGuard } from 'src/api-authorization/authorize.guard'; -import { AuthorizeInterceptor } from 'src/api-authorization/authorize.interceptor'; -import { ReportDesignerComponent } from './reportdesigner/report-designer'; -import { ReportViewerComponent } from './reportviewer/report-viewer'; - -@NgModule({ - declarations: [ - AppComponent, - NavMenuComponent, - HomeComponent, - ReportViewerComponent, - ReportDesignerComponent, - ReportListComponent - ], - imports: [ - BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), - HttpClientModule, - FormsModule, - DxReportViewerModule, - DxReportDesignerModule, - ApiAuthorizationModule, - RouterModule.forRoot([ - { path: '', component: HomeComponent, pathMatch: 'full' }, - { path: 'designer', component: ReportDesignerComponent, canActivate: [AuthorizeGuard] }, - { path: 'viewer', component: ReportViewerComponent, canActivate: [AuthorizeGuard] }, - { path: 'report-list', component: ReportListComponent, canActivate: [AuthorizeGuard]}, - ]) - ], - providers: [ - { provide: HTTP_INTERCEPTORS, useClass: AuthorizeInterceptor, multi: true } - ], - bootstrap: [AppComponent] -}) -export class AppModule { } +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { NavMenuComponent } from './nav-menu/nav-menu.component'; +import { DxReportViewerModule, DxReportDesignerModule } from 'devexpress-reporting-angular'; +import { HomeComponent } from './home/home.component'; +import { ReportListComponent } from './report-list/report.list.component'; + +import { ReportDesignerComponent } from './reportdesigner/report-designer'; +import { ReportViewerComponent } from './reportviewer/report-viewer'; +import { ApiAuthorizationModule } from '../api-authorization/api-authorization.module'; +import { AuthorizeGuard } from '../api-authorization/authorize.guard'; +import { AuthorizeInterceptor } from '../api-authorization/authorize.interceptor'; + +@NgModule({ + declarations: [ + AppComponent, + NavMenuComponent, + HomeComponent, + ReportViewerComponent, + ReportDesignerComponent, + ReportListComponent + ], + imports: [ + BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), + HttpClientModule, + FormsModule, + DxReportViewerModule, + DxReportDesignerModule, + ApiAuthorizationModule, + RouterModule.forRoot([ + { path: '', component: HomeComponent, pathMatch: 'full' }, + { path: 'designer', component: ReportDesignerComponent, canActivate: [AuthorizeGuard] }, + { path: 'viewer', component: ReportViewerComponent, canActivate: [AuthorizeGuard] }, + { path: 'report-list', component: ReportListComponent, canActivate: [AuthorizeGuard]}, + ]) + ], + providers: [ + { provide: HTTP_INTERCEPTORS, useClass: AuthorizeInterceptor, multi: true } + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/app.server.module.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.server.module.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/app.server.module.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.server.module.ts index 613fbf6..316380b 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/app.server.module.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/app.server.module.ts @@ -1,10 +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 { } +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 { } diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/home/home.component.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/home/home.component.html similarity index 81% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/home/home.component.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/home/home.component.html index 0b869a6..a2484d5 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/home/home.component.html +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/home/home.component.html @@ -1,14 +1,14 @@ -

Hello, world!

-

Welcome to your new single-page application, built with:

- -

To help you get started, we've also set up:

-
    -
  • Client-side navigation. For example, click Counter then Back to return here.
  • -
  • Angular CLI integration. In development mode, there's no need to run ng serve. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.
  • -
  • Efficient production builds. In production mode, development-time features are disabled, and your dotnet publish configuration automatically invokes ng build to produce minified, ahead-of-time compiled JavaScript files.
  • -
-

The ClientApp subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any ng command (e.g., ng test), or use npm to install extra packages into it.

+

Hello, world!

+

Welcome to your new single-page application, built with:

+ +

To help you get started, we've also set up:

+
    +
  • Client-side navigation. For example, click Counter then Back to return here.
  • +
  • Angular CLI integration. In development mode, there's no need to run ng serve. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.
  • +
  • Efficient production builds. In production mode, development-time features are disabled, and your dotnet publish configuration automatically invokes ng build to produce minified, ahead-of-time compiled JavaScript files.
  • +
+

The AspNetCore.Reporting.Angular.Client project is a standard Angular CLI application. If you open a command prompt in that directory, you can run any ng command (e.g., ng test), or use npm to install extra packages into it.

diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/home/home.component.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/home/home.component.ts similarity index 95% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/home/home.component.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/home/home.component.ts index e5d496a..2747b30 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/home/home.component.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/home/home.component.ts @@ -1,8 +1,8 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-home', - templateUrl: './home.component.html', -}) -export class HomeComponent { -} +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', +}) +export class HomeComponent { +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.css b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.css similarity index 93% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.css rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.css index 8592457..10389ef 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.css +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.css @@ -1,18 +1,18 @@ -a.navbar-brand { - white-space: normal; - text-align: center; - word-break: break-all; -} - -html { - font-size: 14px; -} -@media (min-width: 768px) { - html { - font-size: 16px; - } -} - -.box-shadow { - box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); -} +a.navbar-brand { + white-space: normal; + text-align: center; + word-break: break-all; +} + +html { + font-size: 14px; +} +@media (min-width: 768px) { + html { + font-size: 16px; + } +} + +.box-shadow { + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.html similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.html index 7613370..5b859e3 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.html +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.html @@ -1,40 +1,40 @@ -
- -
+
+ +
diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.ts similarity index 94% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.ts index 0d68605..327a374 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/nav-menu/nav-menu.component.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/nav-menu/nav-menu.component.ts @@ -1,18 +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; - } -} +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; + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/report-list/report.list.component.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/report-list/report.list.component.html similarity index 98% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/report-list/report.list.component.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/report-list/report.list.component.html index 87e1785..6feb998 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/report-list/report.list.component.html +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/report-list/report.list.component.html @@ -1,12 +1,12 @@ -

Reports

-
    -
  • - {{reportItem.title}} -
    -
    - Show - Edit -
    -
    -
  • -
+

Reports

+
    +
  • + {{reportItem.title}} +
    +
    + Show + Edit +
    +
    +
  • +
diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/report-list/report.list.component.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/report-list/report.list.component.ts similarity index 68% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/report-list/report.list.component.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/report-list/report.list.component.ts index 914cb3d..32f5d19 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/report-list/report.list.component.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/report-list/report.list.component.ts @@ -1,20 +1,21 @@ -import { Component, Inject } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; - -@Component({ - selector: 'report-list-component', - templateUrl: './report.list.component.html' -}) -export class ReportListComponent { - reportList?: ReportItem[]; - constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string) { - http.get(baseUrl + 'reportlist').subscribe(result => { - this.reportList = result; - }, error => console.error(error)); - } -} - -interface ReportItem { - id: string; - title: string; -} +import { Component, Inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from '../../environments/environment'; + +@Component({ + selector: 'report-list-component', + templateUrl: './report.list.component.html' +}) +export class ReportListComponent { + reportList?: ReportItem[]; + constructor(http: HttpClient) { + http.get(environment.serverUri + 'reportlist').subscribe(result => { + this.reportList = result; + }, error => console.error(error)); + } +} + +interface ReportItem { + id: string; + title: string; +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/reportdesigner/report-designer.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportdesigner/report-designer.html similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/reportdesigner/report-designer.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportdesigner/report-designer.html index efed0e6..882433e 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/reportdesigner/report-designer.html +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportdesigner/report-designer.html @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/reportdesigner/report-designer.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportdesigner/report-designer.ts similarity index 90% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/reportdesigner/report-designer.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportdesigner/report-designer.ts index 4e7e920..42912b7 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/reportdesigner/report-designer.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportdesigner/report-designer.ts @@ -1,49 +1,51 @@ -import { fetchSetup } from "@devexpress/analytics-core/analytics-utils" -import { Component, Inject, ViewEncapsulation, OnInit } from '@angular/core'; -import { AuthorizeService } from '../../api-authorization/authorize.service'; -import * as ko from 'knockout'; -import { ActivatedRoute } from '@angular/router'; - -@Component({ - selector: 'report-designer', - encapsulation: ViewEncapsulation.None, - templateUrl: './report-designer.html', - styleUrls: [ - "../../../node_modules/ace-builds/css/ace.css", - "../../../node_modules/ace-builds/css/theme/dreamweaver.css", - "../../../node_modules/devextreme/dist/css/dx.material.blue.light.css", - "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.common.css", - "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.material.blue.light.css", - "../../../node_modules/@devexpress/analytics-core/dist/css/dx-querybuilder.css", - "../../../node_modules/devexpress-reporting/dist/css/dx-webdocumentviewer.css", - "../../../node_modules/devexpress-reporting/dist/css/dx-reportdesigner.css" - ] -}) - -export class ReportDesignerComponent implements OnInit { - getDesignerModelAction = "api/ReportDesignerSetup/GetReportDesignerModel"; - get reportUrl() { - return this.koReportUrl(); - }; - set reportUrl(newUrl) { - this.koReportUrl(newUrl); - } - koReportUrl = ko.observable(''); - - constructor(@Inject('BASE_URL') public hostUrl: string, private authorize: AuthorizeService, private activateRoute: ActivatedRoute) { - this.authorize.getAccessToken() - .subscribe(x => { - fetchSetup.fetchSettings = { - headers: { - 'Authorization': 'Bearer ' + x - } - }; - }); - } - - ngOnInit() { - if(this.activateRoute.snapshot.queryParams['reportId']) { - this.reportUrl = this.activateRoute.snapshot.queryParams['reportId']; - } - } -} +import { fetchSetup } from "@devexpress/analytics-core/analytics-utils" +import { Component, Inject, ViewEncapsulation, OnInit } from '@angular/core'; +import { AuthorizeService } from '../../api-authorization/authorize.service'; +import * as ko from 'knockout'; +import { ActivatedRoute } from '@angular/router'; +import { environment } from "../../environments/environment"; + +@Component({ + selector: 'report-designer', + encapsulation: ViewEncapsulation.None, + templateUrl: './report-designer.html', + styleUrls: [ + "../../../node_modules/ace-builds/css/ace.css", + "../../../node_modules/ace-builds/css/theme/dreamweaver.css", + "../../../node_modules/devextreme/dist/css/dx.material.blue.light.css", + "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.common.css", + "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.material.blue.light.css", + "../../../node_modules/@devexpress/analytics-core/dist/css/dx-querybuilder.css", + "../../../node_modules/devexpress-reporting/dist/css/dx-webdocumentviewer.css", + "../../../node_modules/devexpress-reporting/dist/css/dx-reportdesigner.css" + ] +}) + +export class ReportDesignerComponent implements OnInit { + getDesignerModelAction = "api/ReportDesignerSetup/GetReportDesignerModel"; + hostUrl = environment.serverUri; + get reportUrl() { + return this.koReportUrl(); + }; + set reportUrl(newUrl) { + this.koReportUrl(newUrl); + } + koReportUrl = ko.observable(''); + + constructor(private authorize: AuthorizeService, private activateRoute: ActivatedRoute) { + this.authorize.getAccessToken() + .subscribe(x => { + fetchSetup.fetchSettings = { + headers: { + 'Authorization': 'Bearer ' + x + } + }; + }); + } + + ngOnInit() { + if(this.activateRoute.snapshot.queryParams['reportId']) { + this.reportUrl = this.activateRoute.snapshot.queryParams['reportId']; + } + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/reportviewer/report-viewer.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportviewer/report-viewer.html similarity index 98% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/reportviewer/report-viewer.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportviewer/report-viewer.html index ac04860..a36a84c 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/reportviewer/report-viewer.html +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportviewer/report-viewer.html @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/app/reportviewer/report-viewer.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportviewer/report-viewer.ts similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/app/reportviewer/report-viewer.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportviewer/report-viewer.ts index e9eab4e..de433b0 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/app/reportviewer/report-viewer.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/app/reportviewer/report-viewer.ts @@ -1,47 +1,47 @@ -import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { fetchSetup } from '@devexpress/analytics-core/analytics-utils'; -import * as ko from 'knockout'; -import { AuthorizeService } from '../../api-authorization/authorize.service'; - -@Component({ - selector: 'report-viewer', - encapsulation: ViewEncapsulation.None, - templateUrl: './report-viewer.html', - styleUrls: [ - "../../../node_modules/devextreme/dist/css/dx.material.blue.light.css", - "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.common.css", - "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.material.blue.light.css", - "../../../node_modules/devexpress-reporting/dist/css/dx-webdocumentviewer.css" - ] -}) -export class ReportViewerComponent implements OnInit { - get reportUrl() { - return this.koReportUrl(); - }; - set reportUrl(newUrl) { - this.koReportUrl(newUrl); - } - koReportUrl = ko.observable(''); - invokeAction: string = '/DXXRDVAngular'; - - useSameTabExport = true; - useAsynchronousExport = true; - - constructor(@Inject('BASE_URL') public hostUrl: string, private authorize: AuthorizeService, private activateRoute: ActivatedRoute) { - this.authorize.getAccessToken() - .subscribe(x => { - fetchSetup.fetchSettings = { - headers: { - 'Authorization': 'Bearer ' + x - } - }; - }); - } - - ngOnInit() { - if(this.activateRoute.snapshot.queryParams['reportId']) { - this.reportUrl = this.activateRoute.snapshot.queryParams['reportId']; - } - } -} +import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { fetchSetup } from '@devexpress/analytics-core/analytics-utils'; +import * as ko from 'knockout'; +import { AuthorizeService } from '../../api-authorization/authorize.service'; + +@Component({ + selector: 'report-viewer', + encapsulation: ViewEncapsulation.None, + templateUrl: './report-viewer.html', + styleUrls: [ + "../../../node_modules/devextreme/dist/css/dx.material.blue.light.css", + "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.common.css", + "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.material.blue.light.css", + "../../../node_modules/devexpress-reporting/dist/css/dx-webdocumentviewer.css" + ] +}) +export class ReportViewerComponent implements OnInit { + get reportUrl() { + return this.koReportUrl(); + }; + set reportUrl(newUrl) { + this.koReportUrl(newUrl); + } + koReportUrl = ko.observable(''); + invokeAction: string = '/DXXRDVAngular'; + + useSameTabExport = true; + useAsynchronousExport = true; + + constructor(@Inject('BASE_URL') public hostUrl: string, private authorize: AuthorizeService, private activateRoute: ActivatedRoute) { + this.authorize.getAccessToken() + .subscribe(x => { + fetchSetup.fetchSettings = { + headers: { + 'Authorization': 'Bearer ' + x + } + }; + }); + } + + ngOnInit() { + if(this.activateRoute.snapshot.queryParams['reportId']) { + this.reportUrl = this.activateRoute.snapshot.queryParams['reportId']; + } + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/assets/.gitkeep b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/assets/.gitkeep similarity index 100% rename from AspNetCore.Reporting.Angular/ClientApp/src/assets/.gitkeep rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/assets/.gitkeep diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/environments/environment.prod.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/environments/environment.prod.ts new file mode 100644 index 0000000..f53ae42 --- /dev/null +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/environments/environment.prod.ts @@ -0,0 +1,4 @@ +export const environment = { + production: true, + serverUri: 'https://localhost:5001' +}; diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/environments/environment.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/environments/environment.ts similarity index 91% rename from AspNetCore.Reporting.Angular/ClientApp/src/environments/environment.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/environments/environment.ts index 4566f5d..2aa53fd 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/environments/environment.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/environments/environment.ts @@ -1,16 +1,17 @@ -// 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. +// 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, + serverUri: 'https://localhost:5001/' +}; + +/* + * 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. diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/index.html b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/index.html similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/index.html rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/index.html index 27b5206..b89681e 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/index.html +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/index.html @@ -1,14 +1,14 @@ - - - - - AspNetCore.Reporting.Angular - - - - - - - Loading... - - + + + + + AspNetCore.Reporting.Angular + + + + + + + Loading... + + diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/karma.conf.js b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/karma.conf.js similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/karma.conf.js rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/karma.conf.js index d8da2ed..4a9730b 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/karma.conf.js +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/karma.conf.js @@ -1,31 +1,31 @@ -// 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-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, '../coverage'), - reports: ['html', 'lcovonly'], - fixWebpackSourcePaths: true - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false - }); -}; +// 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-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage'), + reports: ['html', 'lcovonly'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/main.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/main.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/main.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/main.ts index 06df183..a2f708c 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/main.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/main.ts @@ -1,20 +1,20 @@ -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)); +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)); diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/polyfills.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/polyfills.ts similarity index 97% rename from AspNetCore.Reporting.Angular/ClientApp/src/polyfills.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/polyfills.ts index 6baf413..373f538 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/polyfills.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/polyfills.ts @@ -1,65 +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 - */ +/** + * 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 + */ diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/proxy.conf.js b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/proxy.conf.js new file mode 100644 index 0000000..0269049 --- /dev/null +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/proxy.conf.js @@ -0,0 +1,16 @@ +const { env } = require('process'); + +const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` : + env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'https://localhost:64522/'; + +const PROXY_CONFIG = [ + { + context: [ + "/DXXRD", "/DXXQB", "DXXRDV" + ], + target, + secure: false + } +] + +module.exports = PROXY_CONFIG; \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/styles.css b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/styles.css similarity index 94% rename from AspNetCore.Reporting.Angular/ClientApp/src/styles.css rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/styles.css index 7031fa6..3ef6a64 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/styles.css +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/styles.css @@ -1,16 +1,16 @@ -/* You can add global styles to this file, and also import other style files */ - -/* Provide sufficient contrast against white background */ -a { - color: #0366d6; -} - -code { - color: #e01a76; -} - -.btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} +/* You can add global styles to this file, and also import other style files */ + +/* Provide sufficient contrast against white background */ +a { + color: #0366d6; +} + +code { + color: #e01a76; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/test.ts b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/test.ts similarity index 96% rename from AspNetCore.Reporting.Angular/ClientApp/src/test.ts rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/test.ts index 26cfdde..1631789 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/src/test.ts +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/src/test.ts @@ -1,20 +1,20 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/dist/zone-testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: any; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting() -); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tsconfig.app.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tsconfig.app.json new file mode 100644 index 0000000..0030beb --- /dev/null +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tsconfig.app.json @@ -0,0 +1,16 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/tsconfig.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tsconfig.json similarity index 65% rename from AspNetCore.Reporting.Angular/ClientApp/tsconfig.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tsconfig.json index 3fe65bc..58281f5 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/tsconfig.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tsconfig.json @@ -1,30 +1,31 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "moduleResolution": "node", - "importHelpers": true, - "target": "es2017", - "module": "es2020", - "lib": [ - "es2018", - "dom" - ] - }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - } -} +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "lib": [ + "ES2022", + "dom" + ] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/AspNetCore.Reporting.Angular/ClientApp/tslint.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tslint.json similarity index 95% rename from AspNetCore.Reporting.Angular/ClientApp/tslint.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tslint.json index dca5154..f5f06e9 100644 --- a/AspNetCore.Reporting.Angular/ClientApp/tslint.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Client/tslint.json @@ -1,130 +1,130 @@ -{ - "rulesDirectory": [ - "node_modules/codelyzer" - ], - "rules": { - "arrow-return-shorthand": true, - "callable-types": true, - "class-name": true, - "comment-format": [ - true, - "check-space" - ], - "curly": true, - "deprecation": { - "severity": "warn" - }, - "eofline": true, - "forin": true, - "import-blacklist": [ - true, - "rxjs/Rx" - ], - "import-spacing": true, - "indent": [ - true, - "spaces" - ], - "interface-over-type-literal": true, - "label-position": true, - "max-line-length": [ - true, - 140 - ], - "member-access": false, - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-arg": true, - "no-bitwise": true, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-super": true, - "no-empty": false, - "no-empty-interface": true, - "no-eval": true, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-misused-new": true, - "no-non-null-assertion": true, - "no-shadowed-variable": true, - "no-string-literal": false, - "no-string-throw": true, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unnecessary-initializer": true, - "no-unused-expression": true, - "no-use-before-declare": true, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "one-line": [ - true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" - ], - "prefer-const": true, - "quotemark": [ - true, - "single" - ], - "radix": true, - "semicolon": [ - true, - "always" - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "unified-signatures": true, - "variable-name": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ], - "no-output-on-prefix": true, - "no-inputs-metadata-property": true, - "no-outputs-metadata-property": true, - "no-host-metadata-property": true, - "no-input-rename": true, - "no-output-rename": true, - "use-lifecycle-interface": true, - "use-pipe-transform-interface": true, - "component-class-suffix": true, - "directive-class-suffix": true - } -} +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "deprecation": { + "severity": "warn" + }, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": [ + true, + "spaces" + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-misused-new": true, + "no-non-null-assertion": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unnecessary-initializer": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-output-on-prefix": true, + "no-inputs-metadata-property": true, + "no-outputs-metadata-property": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-output-rename": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true + } +} diff --git a/AspNetCore.Reporting.Angular/.gitignore b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/.gitignore similarity index 93% rename from AspNetCore.Reporting.Angular/.gitignore rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/.gitignore index 3255fe7..41ffa34 100644 --- a/AspNetCore.Reporting.Angular/.gitignore +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/.gitignore @@ -1,231 +1,231 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -bin/ -Bin/ -obj/ -Obj/ - -# Visual Studio 2015 cache/options directory -.vs/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Microsoft Azure ApplicationInsights config file -ApplicationInsights.config - -# Windows Store app package directory -AppPackages/ -BundleArtifacts/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -orleans.codegen.cs - -/node_modules - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe - -# FAKE - F# Make -.fake/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +bin/ +Bin/ +obj/ +Obj/ + +# Visual Studio 2015 cache/options directory +.vs/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +orleans.codegen.cs + +/node_modules + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/IdentityHostingStartup.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/IdentityHostingStartup.cs new file mode 100644 index 0000000..0483ae9 --- /dev/null +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/IdentityHostingStartup.cs @@ -0,0 +1,9 @@ +[assembly: HostingStartup(typeof(AspNetCore.Reporting.Angular.Areas.Identity.IdentityHostingStartup))] +namespace AspNetCore.Reporting.Angular.Areas.Identity { + public class IdentityHostingStartup : IHostingStartup { + public void Configure(IWebHostBuilder builder) { + builder.ConfigureServices((context, services) => { + }); + } + } +} diff --git a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/Account/Login.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/Account/Login.cshtml similarity index 97% rename from AspNetCore.Reporting.Angular/Areas/Identity/Pages/Account/Login.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/Account/Login.cshtml index 44735df..96fd805 100644 --- a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/Account/Login.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/Account/Login.cshtml @@ -1,85 +1,85 @@ -@page -@model LoginModel - -@{ - ViewData["Title"] = "Log in"; -} - -

@ViewData["Title"]

-
-
-
-
-

Use a local account to log in.

-
- @*
*@ -
- - - - @* - - *@ -
- @*
- - - -
*@ -
-
- -
-
-
- -
- -
-
-
-
-
-

Use another service to log in.

-
- @{ - if ((Model.ExternalLogins?.Count ?? 0) == 0) - { -
-

- There are no external authentication services configured. See this article - for details on setting up this ASP.NET application to support logging in via external services. -

-
- } - else - { -
-
-

- @foreach (var provider in Model.ExternalLogins) - { - - } -

-
-
- } - } -
-
-
- -@section Scripts { - -} +@page +@model LoginModel + +@{ + ViewData["Title"] = "Log in"; +} + +

@ViewData["Title"]

+
+
+
+
+

Use a local account to log in.

+
+ @*
*@ +
+ + + + @* + + *@ +
+ @*
+ + + +
*@ +
+
+ +
+
+
+ +
+ +
+
+
+
+
+

Use another service to log in.

+
+ @{ + if ((Model.ExternalLogins?.Count ?? 0) == 0) + { +
+

+ There are no external authentication services configured. See this article + for details on setting up this ASP.NET application to support logging in via external services. +

+
+ } + else + { +
+
+

+ @foreach (var provider in Model.ExternalLogins) + { + + } +

+
+
+ } + } +
+
+
+ +@section Scripts { + +} diff --git a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/Account/Login.cshtml.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/Account/Login.cshtml.cs similarity index 93% rename from AspNetCore.Reporting.Angular/Areas/Identity/Pages/Account/Login.cshtml.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/Account/Login.cshtml.cs index 677ca2c..3615564 100644 --- a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -1,125 +1,118 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using AspNetCore.Reporting.Common.Data; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.UI.Services; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Logging; -using Microsoft.AspNetCore.Mvc.Rendering; -using System.Security.Claims; - -namespace AspNetCore.Reporting.Angular.Areas.Identity.Pages.Account { - [AllowAnonymous] - public class LoginModel : PageModel { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public LoginModel(SignInManager signInManager, - ILogger logger, - UserManager userManager) { - _userManager = userManager; - _signInManager = signInManager; - _logger = logger; - } - - [BindProperty] - public InputModel Input { get; set; } - - public IList ExternalLogins { get; set; } - - public string ReturnUrl { get; set; } - - public IEnumerable Users { get; set; } - - [TempData] - public string ErrorMessage { get; set; } - - public class InputModel { - //[Required] - //[EmailAddress] - //public string Email { get; set; } - - [Required] - public string UserId { get; set; } - - //[Required] - //[DataType(DataType.Password)] - //public string Password { get; set; } - - [Display(Name = "Remember me?")] - public bool RememberMe { get; set; } - } - - public async Task OnGetAsync(string returnUrl = null) { - if(!string.IsNullOrEmpty(ErrorMessage)) { - ModelState.AddModelError(string.Empty, ErrorMessage); - } - - returnUrl = returnUrl ?? Url.Content("~/"); - - Users = _userManager.Users.Select(x => new SelectListItem(x.FirstMidName + " " + x.LastName, x.Id)); - // Clear the existing external cookie to ensure a clean login process - await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); - - ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); - - ReturnUrl = returnUrl; - } - - public async Task OnPostAsync(string returnUrl = null) { - returnUrl = returnUrl ?? Url.Content("~/"); - - if(ModelState.IsValid) { - // This doesn't count login failures towards account lockout - // To enable password failures to trigger account lockout, set lockoutOnFailure: true - var user = await _userManager.FindByIdAsync(Input.UserId); - await _signInManager.SignInAsync(user, Input.RememberMe); - var userClaims = await _userManager.GetClaimsAsync(user); - string userName = $"{user.FirstMidName} {user.LastName}"; - userClaims.Add(new Claim(ClaimTypes.Name, userName)); - userClaims.Add(new Claim(ClaimTypes.NameIdentifier, userName)); - userClaims.Add(new Claim(ClaimTypes.Role, "User")); - userClaims.Add(new Claim(ClaimTypes.Sid, user.Id)); - var identityResult1 = await _userManager.AddClaimsAsync(user, userClaims); - - var claims = new List { - new Claim(ClaimTypes.Name, userName), - new Claim(ClaimTypes.NameIdentifier, userName), - new Claim(ClaimTypes.Role, "User"), - new Claim(ClaimTypes.Sid, user.Id) - }; - var identityResult2 = await _userManager.AddClaimsAsync(user, claims); - - _logger.LogInformation("User logged in."); - return LocalRedirect(returnUrl); - - //var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); - //if(result.Succeeded) { - // _logger.LogInformation("User logged in."); - // return LocalRedirect(returnUrl); - //} - //if(result.RequiresTwoFactor) { - // return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); - //} - //if(result.IsLockedOut) { - // _logger.LogWarning("User account locked out."); - // return RedirectToPage("./Lockout"); - //} else { - // ModelState.AddModelError(string.Empty, "Invalid login attempt."); - // return Page(); - //} - } - - // If we got this far, something failed, redisplay form - return Page(); - } - } -} +using System.ComponentModel.DataAnnotations; +using System.Security.Claims; +using AspNetCore.Reporting.Common.Data; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace AspNetCore.Reporting.Angular.Areas.Identity.Pages.Account { + [AllowAnonymous] + public class LoginModel : PageModel { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public LoginModel(SignInManager signInManager, + ILogger logger, + UserManager userManager) { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + } + + [BindProperty] + public InputModel Input { get; set; } + + public IList ExternalLogins { get; set; } + + public string ReturnUrl { get; set; } + + public IEnumerable Users { get; set; } + + [TempData] + public string ErrorMessage { get; set; } + + public class InputModel { + //[Required] + //[EmailAddress] + //public string Email { get; set; } + + [Required] + public string UserId { get; set; } + + //[Required] + //[DataType(DataType.Password)] + //public string Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } + + public async Task OnGetAsync(string returnUrl = null) { + if(!string.IsNullOrEmpty(ErrorMessage)) { + ModelState.AddModelError(string.Empty, ErrorMessage); + } + + returnUrl = returnUrl ?? Url.Content("~/"); + + Users = _userManager.Users.Select(x => new SelectListItem(x.FirstMidName + " " + x.LastName, x.Id)); + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + + ReturnUrl = returnUrl; + } + + public async Task OnPostAsync(string returnUrl = null) { + returnUrl = returnUrl ?? Url.Content("~/"); + + if(ModelState.IsValid) { + // This doesn't count login failures towards account lockout + // To enable password failures to trigger account lockout, set lockoutOnFailure: true + var user = await _userManager.FindByIdAsync(Input.UserId); + await _signInManager.SignInAsync(user, Input.RememberMe); + var userClaims = await _userManager.GetClaimsAsync(user); + string userName = $"{user.FirstMidName} {user.LastName}"; + userClaims.Add(new Claim(ClaimTypes.Name, userName)); + userClaims.Add(new Claim(ClaimTypes.NameIdentifier, userName)); + userClaims.Add(new Claim(ClaimTypes.Role, "User")); + userClaims.Add(new Claim(ClaimTypes.Sid, user.Id)); + var identityResult1 = await _userManager.AddClaimsAsync(user, userClaims); + + var claims = new List { + new Claim(ClaimTypes.Name, userName), + new Claim(ClaimTypes.NameIdentifier, userName), + new Claim(ClaimTypes.Role, "User"), + new Claim(ClaimTypes.Sid, user.Id) + }; + var identityResult2 = await _userManager.AddClaimsAsync(user, claims); + + _logger.LogInformation("User logged in."); + return LocalRedirect(returnUrl); + + //var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); + //if(result.Succeeded) { + // _logger.LogInformation("User logged in."); + // return LocalRedirect(returnUrl); + //} + //if(result.RequiresTwoFactor) { + // return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); + //} + //if(result.IsLockedOut) { + // _logger.LogWarning("User account locked out."); + // return RedirectToPage("./Lockout"); + //} else { + // ModelState.AddModelError(string.Empty, "Invalid login attempt."); + // return Page(); + //} + } + + // If we got this far, something failed, redisplay form + return Page(); + } + } +} diff --git a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/Account/_ViewImports.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/Account/_ViewImports.cshtml similarity index 100% rename from AspNetCore.Reporting.Angular/Areas/Identity/Pages/Account/_ViewImports.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/Account/_ViewImports.cshtml diff --git a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/_ViewImports.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/_ViewImports.cshtml similarity index 97% rename from AspNetCore.Reporting.Angular/Areas/Identity/Pages/_ViewImports.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/_ViewImports.cshtml index 9f0ce4d..072b408 100644 --- a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/_ViewImports.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/_ViewImports.cshtml @@ -1,5 +1,5 @@ -@using Microsoft.AspNetCore.Identity -@using AspNetCore.Reporting.Angular.Areas.Identity -@using AspNetCore.Reporting.Angular.Areas.Identity.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@using AspNetCore.Reporting.Common.Data +@using Microsoft.AspNetCore.Identity +@using AspNetCore.Reporting.Angular.Areas.Identity +@using AspNetCore.Reporting.Angular.Areas.Identity.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@using AspNetCore.Reporting.Common.Data diff --git a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/_ViewStart.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/_ViewStart.cshtml similarity index 93% rename from AspNetCore.Reporting.Angular/Areas/Identity/Pages/_ViewStart.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/_ViewStart.cshtml index 4c70e20..55cd025 100644 --- a/AspNetCore.Reporting.Angular/Areas/Identity/Pages/_ViewStart.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Areas/Identity/Pages/_ViewStart.cshtml @@ -1,4 +1,4 @@ - -@{ - Layout = "/Pages/Shared/_Layout.cshtml"; -} + +@{ + Layout = "/Pages/Shared/_Layout.cshtml"; +} diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.csproj b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/AspNetCore.Reporting.Angular.Server.csproj similarity index 72% rename from AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.csproj rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/AspNetCore.Reporting.Angular.Server.csproj index 92dc5b5..157eee6 100644 --- a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.csproj +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/AspNetCore.Reporting.Angular.Server.csproj @@ -1,67 +1,63 @@ - - - net8.0 - true - Latest - false - ClientApp\ - $(DefaultItemExcludes);$(SpaRoot)node_modules\** - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - %(DistFiles.Identity) - PreserveNewest - true - - - + + + net8.0 + enable + ..\AspNetCore.Reporting.Angular.Client\ + + false + npm start + https://localhost:4200 + a492d679-1af6-4b19-ae72-9deeef5f7510 + + + + + + + + + + + + + + + + + + + + false + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + %(DistFiles.Identity) + PreserveNewest + true + + + \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/Controllers/CustomAngularReportingControllers.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/CustomAngularReportingControllers.cs similarity index 97% rename from AspNetCore.Reporting.Angular/Controllers/CustomAngularReportingControllers.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/CustomAngularReportingControllers.cs index 7ffbc50..89534a1 100644 --- a/AspNetCore.Reporting.Angular/Controllers/CustomAngularReportingControllers.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/CustomAngularReportingControllers.cs @@ -1,34 +1,32 @@ -using DevExpress.AspNetCore.Reporting.QueryBuilder; -using DevExpress.AspNetCore.Reporting.QueryBuilder.Native.Services; -using DevExpress.AspNetCore.Reporting.ReportDesigner; -using DevExpress.AspNetCore.Reporting.ReportDesigner.Native.Services; -using DevExpress.AspNetCore.Reporting.WebDocumentViewer; -using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace AspNetCore.Reporting.Common.Controllers { - [Authorize] - [Route("DXXRDVAngular")] - public class AngularWebDocumentViewerController : WebDocumentViewerController { - public AngularWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService) : base(controllerService) { - } - - - } - - [Authorize] - [Route("DXXQBAngular")] - public class AngularQueryBuilderController : QueryBuilderController { - public AngularQueryBuilderController(IQueryBuilderMvcControllerService controllerService) : base(controllerService) { - } - } - - - [Route("DXXRDAngular")] - [Authorize] - public class AngularReportDesignerController : ReportDesignerController { - public AngularReportDesignerController(IReportDesignerMvcControllerService controllerService) : base(controllerService) { - } - } -} +using DevExpress.AspNetCore.Reporting.QueryBuilder; +using DevExpress.AspNetCore.Reporting.QueryBuilder.Native.Services; +using DevExpress.AspNetCore.Reporting.ReportDesigner; +using DevExpress.AspNetCore.Reporting.ReportDesigner.Native.Services; +using DevExpress.AspNetCore.Reporting.WebDocumentViewer; +using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace AspNetCore.Reporting.Common.Controllers { + [Authorize] + [Route("DXXRDVAngular")] + public class AngularWebDocumentViewerController : WebDocumentViewerController { + public AngularWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService) : base(controllerService) { + } + } + + [Authorize] + [Route("DXXQBAngular")] + public class AngularQueryBuilderController : QueryBuilderController { + public AngularQueryBuilderController(IQueryBuilderMvcControllerService controllerService) : base(controllerService) { + } + } + + + [Route("DXXRDAngular")] + [Authorize] + public class AngularReportDesignerController : ReportDesignerController { + public AngularReportDesignerController(IReportDesignerMvcControllerService controllerService) : base(controllerService) { + } + } +} diff --git a/AspNetCore.Reporting.Angular/Controllers/OidcConfigurationController.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/OidcConfigurationController.cs similarity index 94% rename from AspNetCore.Reporting.Angular/Controllers/OidcConfigurationController.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/OidcConfigurationController.cs index 7eeb86a..b52d307 100644 --- a/AspNetCore.Reporting.Angular/Controllers/OidcConfigurationController.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/OidcConfigurationController.cs @@ -1,22 +1,21 @@ -using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace AspNetCore.Reporting.Angular.Controllers { - public class OidcConfigurationController : Controller { - private readonly ILogger logger; - - public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger _logger) { - ClientRequestParametersProvider = clientRequestParametersProvider; - logger = _logger; - } - - public IClientRequestParametersProvider ClientRequestParametersProvider { get; } - - [HttpGet("_configuration/{clientId}")] - public IActionResult GetClientRequestParameters([FromRoute] string clientId) { - var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); - return Ok(parameters); - } - } -} +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.AspNetCore.Mvc; + +namespace AspNetCore.Reporting.Angular.Controllers { + public class OidcConfigurationController : Controller { + private readonly ILogger logger; + + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger _logger) { + ClientRequestParametersProvider = clientRequestParametersProvider; + logger = _logger; + } + + public IClientRequestParametersProvider ClientRequestParametersProvider { get; } + + [HttpGet("_configuration/{clientId}")] + public IActionResult GetClientRequestParameters([FromRoute] string clientId) { + var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); + return Ok(parameters); + } + } +} diff --git a/AspNetCore.Reporting.Angular/Controllers/ReportDesignerController.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/ReportDesignerController.cs similarity index 95% rename from AspNetCore.Reporting.Angular/Controllers/ReportDesignerController.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/ReportDesignerController.cs index 18ef2f5..0b28456 100644 --- a/AspNetCore.Reporting.Angular/Controllers/ReportDesignerController.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/ReportDesignerController.cs @@ -1,31 +1,30 @@ -using System.Collections.Generic; -using System.Net.Mime; -using DevExpress.XtraReports.Web.ReportDesigner; -using DevExpress.XtraReports.Web.ReportDesigner.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace AspNetCore.Reporting.Common.Controllers { - [ApiController] - [Authorize] - [Route("api/[controller]")] - public class ReportDesignerSetupController : ControllerBase { - [HttpPost("[action]")] - public object GetReportDesignerModel([FromForm] string reportUrl, - [FromServices] IReportDesignerModelBuilder reportDesignerModel, - [FromServices] IReportDesignerClientSideModelGenerator modelGenerator) { - Dictionary dataSources = new Dictionary(); - //Fill a data source set if needed - reportDesignerModel - .Report(reportUrl) - .DataSources(dataSources) - .DesignerUri("/DXXRDAngular") - .ViewerUri("/DXXRDVAngular") - .QueryBuilderUri("/DXXQBAngular") - .BuildJsonModel(); - var model = reportDesignerModel.BuildModel(); - var modelJson = modelGenerator.GetJsonModelScript(model); - return Content(modelJson, MediaTypeNames.Application.Json); - } - } -} +using System.Net.Mime; +using DevExpress.XtraReports.Web.ReportDesigner; +using DevExpress.XtraReports.Web.ReportDesigner.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace AspNetCore.Reporting.Common.Controllers { + [ApiController] + [Authorize] + [Route("api/[controller]")] + public class ReportDesignerSetupController : ControllerBase { + [HttpPost("[action]")] + public object GetReportDesignerModel([FromForm] string reportUrl, + [FromServices] IReportDesignerModelBuilder reportDesignerModel, + [FromServices] IReportDesignerClientSideModelGenerator modelGenerator) { + Dictionary dataSources = new Dictionary(); + //Fill a data source set if needed + reportDesignerModel + .Report(reportUrl) + .DataSources(dataSources) + .DesignerUri("/DXXRDAngular") + .ViewerUri("/DXXRDVAngular") + .QueryBuilderUri("/DXXQBAngular") + .BuildJsonModel(); + var model = reportDesignerModel.BuildModel(); + var modelJson = modelGenerator.GetJsonModelScript(model); + return Content(modelJson, MediaTypeNames.Application.Json); + } + } +} diff --git a/AspNetCore.Reporting.Angular/Controllers/ReportListController.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/ReportListController.cs similarity index 91% rename from AspNetCore.Reporting.Angular/Controllers/ReportListController.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/ReportListController.cs index 3b58a09..293febc 100644 --- a/AspNetCore.Reporting.Angular/Controllers/ReportListController.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Controllers/ReportListController.cs @@ -1,31 +1,28 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AspNetCore.Reporting.Angular.Data; -using AspNetCore.Reporting.Common.Models; -using AspNetCore.Reporting.Common.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace AspNetCore.Reporting.Common.Controllers { - [Authorize] - [ApiController] - [Route("[controller]")] - public class ReportListController : ControllerBase { - [HttpGet] - public async Task> Get([FromServices] SchoolDbContext dbContext, [FromServices] IAuthenticatiedUserService userService) { - var reportData = !User.Identity.IsAuthenticated - ? Enumerable.Empty() - : await dbContext - .Reports - .Where(a => a.Student.Id == userService.GetCurrentUserId()) - .Select(a => new ReportingControlModel { - Id = a.ID.ToString(), - Title = string.IsNullOrEmpty(a.DisplayName) ? "Noname Report" : a.DisplayName - }) - .ToListAsync(); - return reportData; - } - } -} +using AspNetCore.Reporting.Angular.Data; +using AspNetCore.Reporting.Common.Models; +using AspNetCore.Reporting.Common.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace AspNetCore.Reporting.Common.Controllers { + [Authorize] + [ApiController] + [Route("[controller]")] + public class ReportListController : ControllerBase { + [HttpGet] + public async Task> Get([FromServices] SchoolDbContext dbContext, [FromServices] IAuthenticatiedUserService userService) { + var reportData = !User.Identity.IsAuthenticated + ? Enumerable.Empty() + : await dbContext + .Reports + .Where(a => a.Student.Id == userService.GetCurrentUserId()) + .Select(a => new ReportingControlModel { + Id = a.ID.ToString(), + Title = string.IsNullOrEmpty(a.DisplayName) ? "Noname Report" : a.DisplayName + }) + .ToListAsync(); + return reportData; + } + } +} diff --git a/AspNetCore.Reporting.Angular/Data/DbInitializer.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Data/DbInitializer.cs similarity index 97% rename from AspNetCore.Reporting.Angular/Data/DbInitializer.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Data/DbInitializer.cs index 0a592cc..e5065d2 100644 --- a/AspNetCore.Reporting.Angular/Data/DbInitializer.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Data/DbInitializer.cs @@ -1,52 +1,52 @@ -using System.IO; -using System.Linq; -using AspNetCore.Reporting.Common.Data; -using AspNetCore.Reporting.Common.Reports; -using DevExpress.XtraReports.UI; -using Microsoft.AspNetCore.Identity; - -namespace AspNetCore.Reporting.Angular.Data { - public static class DbInitializer { - public static void Initialize(SchoolDbContext context, UserManager userManager, ReportsFactory factory) { - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - - // Look for any students. - if(context.Students.Any()) { - return; // DB has been seeded - } - - var students = DbDefaultsGenerator.GenerateStudents(userManager); - foreach(StudentIdentity s in students) { - - context.Students.Add(s); - foreach(var report in factory.Reports.Select(a => new { a.Key, Value = a.Value() })) { - context.Reports.Add(new ReportEntity() { - DisplayName = string.IsNullOrEmpty(report.Value.DisplayName) ? report.Key : report.Value.DisplayName, - ReportLayout = ReportToByteArray(report.Value), - Student = s - }); - } - } - context.SaveChanges(); - var courses = DbDefaultsGenerator.GetCourses(); - foreach(Course c in courses) { - context.Courses.Add(c); - } - context.SaveChanges(); - - var enrollments = DbDefaultsGenerator.GetEnrollments(students, courses); - foreach(Enrollment e in enrollments) { - context.Enrollments.Add(e); - } - context.SaveChanges(); - } - - static byte[] ReportToByteArray(XtraReport report) { - using(var memoryStream = new MemoryStream()) { - report.SaveLayoutToXml(memoryStream); - return memoryStream.ToArray(); - } - } - } -} +using System.IO; +using System.Linq; +using AspNetCore.Reporting.Common.Data; +using AspNetCore.Reporting.Common.Reports; +using DevExpress.XtraReports.UI; +using Microsoft.AspNetCore.Identity; + +namespace AspNetCore.Reporting.Angular.Data { + public static class DbInitializer { + public static void Initialize(SchoolDbContext context, UserManager userManager, ReportsFactory factory) { + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + + // Look for any students. + if(context.Students.Any()) { + return; // DB has been seeded + } + + var students = DbDefaultsGenerator.GenerateStudents(userManager); + foreach(StudentIdentity s in students) { + + context.Students.Add(s); + foreach(var report in factory.Reports.Select(a => new { a.Key, Value = a.Value() })) { + context.Reports.Add(new ReportEntity() { + DisplayName = string.IsNullOrEmpty(report.Value.DisplayName) ? report.Key : report.Value.DisplayName, + ReportLayout = ReportToByteArray(report.Value), + Student = s + }); + } + } + context.SaveChanges(); + var courses = DbDefaultsGenerator.GetCourses(); + foreach(Course c in courses) { + context.Courses.Add(c); + } + context.SaveChanges(); + + var enrollments = DbDefaultsGenerator.GetEnrollments(students, courses); + foreach(Enrollment e in enrollments) { + context.Enrollments.Add(e); + } + context.SaveChanges(); + } + + static byte[] ReportToByteArray(XtraReport report) { + using(var memoryStream = new MemoryStream()) { + report.SaveLayoutToXml(memoryStream); + return memoryStream.ToArray(); + } + } + } +} diff --git a/AspNetCore.Reporting.Angular/Data/SchoolDbContext.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Data/SchoolDbContext.cs similarity index 97% rename from AspNetCore.Reporting.Angular/Data/SchoolDbContext.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Data/SchoolDbContext.cs index fa4c812..fef0f2f 100644 --- a/AspNetCore.Reporting.Angular/Data/SchoolDbContext.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Data/SchoolDbContext.cs @@ -1,27 +1,27 @@ -using AspNetCore.Reporting.Common.Data; -using Duende.IdentityServer.EntityFramework.Options; -using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; - -namespace AspNetCore.Reporting.Angular.Data { - public class SchoolDbContext : ApiAuthorizationDbContext, IStudentEntityProvider, IReportEntityProvider { - public SchoolDbContext( - DbContextOptions options, - IOptions operationalStoreOptions) : base(options, operationalStoreOptions) { - } - - public DbSet Courses { get; set; } - public DbSet Enrollments { get; set; } - public DbSet Students { get; set; } - public DbSet Reports { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) { - base.OnModelCreating(modelBuilder); - modelBuilder.Entity().ToTable("Course"); - modelBuilder.Entity().ToTable("Enrollment"); - modelBuilder.Entity().ToTable("Student"); - modelBuilder.Entity().ToTable("Report"); - } - } -} +using AspNetCore.Reporting.Common.Data; +using Duende.IdentityServer.EntityFramework.Options; +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace AspNetCore.Reporting.Angular.Data { + public class SchoolDbContext : ApiAuthorizationDbContext, IStudentEntityProvider, IReportEntityProvider { + public SchoolDbContext( + DbContextOptions options, + IOptions operationalStoreOptions) : base(options, operationalStoreOptions) { + } + + public DbSet Courses { get; set; } + public DbSet Enrollments { get; set; } + public DbSet Students { get; set; } + public DbSet Reports { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().ToTable("Course"); + modelBuilder.Entity().ToTable("Enrollment"); + modelBuilder.Entity().ToTable("Student"); + modelBuilder.Entity().ToTable("Report"); + } + } +} diff --git a/AspNetCore.Reporting.Angular/Database/application.db b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Database/application.db similarity index 100% rename from AspNetCore.Reporting.Angular/Database/application.db rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Database/application.db diff --git a/AspNetCore.Reporting.Angular/Database/nwind.db b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Database/nwind.db similarity index 100% rename from AspNetCore.Reporting.Angular/Database/nwind.db rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Database/nwind.db diff --git a/AspNetCore.Reporting.Angular/Database/nwind.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Database/nwind.json similarity index 96% rename from AspNetCore.Reporting.Angular/Database/nwind.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Database/nwind.json index 1c6fea5..49aa018 100644 --- a/AspNetCore.Reporting.Angular/Database/nwind.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Database/nwind.json @@ -1,1005 +1,1005 @@ -{ - "Products": [ - { - "ProductID": 1, - "ProductName": "Chai", - "SupplierID": 1, - "CategoryID": 1, - "QuantityPerUnit": "10 boxes x 20 bags", - "UnitPrice": 18, - "UnitsInStock": 39, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900001" - }, - { - "ProductID": 2, - "ProductName": "Chang", - "SupplierID": 1, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 19, - "UnitsInStock": 17, - "UnitsOnOrder": 40, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900002" - }, - { - "ProductID": 3, - "ProductName": "Aniseed Syrup", - "SupplierID": 1, - "CategoryID": 2, - "QuantityPerUnit": "12 - 550 ml bottles", - "UnitPrice": 10, - "UnitsInStock": 13, - "UnitsOnOrder": 70, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900003" - }, - { - "ProductID": 4, - "ProductName": "Chef Anton's Cajun Seasoning", - "SupplierID": 2, - "CategoryID": 2, - "QuantityPerUnit": "48 - 6 oz jars", - "UnitPrice": 22, - "UnitsInStock": 53, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900004" - }, - { - "ProductID": 5, - "ProductName": "Chef Anton's Gumbo Mix", - "SupplierID": 2, - "CategoryID": 2, - "QuantityPerUnit": "36 boxes", - "UnitPrice": 21.35, - "UnitsInStock": 0, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900005" - }, - { - "ProductID": 6, - "ProductName": "Grandma's Boysenberry Spread", - "SupplierID": 3, - "CategoryID": 2, - "QuantityPerUnit": "12 - 8 oz jars", - "UnitPrice": 25, - "UnitsInStock": 120, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900006" - }, - { - "ProductID": 7, - "ProductName": "Uncle Bob's Organic Dried Pears", - "SupplierID": 3, - "CategoryID": 7, - "QuantityPerUnit": "12 - 1 lb pkgs.", - "UnitPrice": 30, - "UnitsInStock": 15, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900007" - }, - { - "ProductID": 8, - "ProductName": "Northwoods Cranberry Sauce", - "SupplierID": 3, - "CategoryID": 2, - "QuantityPerUnit": "12 - 12 oz jars", - "UnitPrice": 40, - "UnitsInStock": 6, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900008" - }, - { - "ProductID": 9, - "ProductName": "Mishi Kobe Niku", - "SupplierID": 4, - "CategoryID": 6, - "QuantityPerUnit": "18 - 500 g pkgs.", - "UnitPrice": 97, - "UnitsInStock": 29, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900009" - }, - { - "ProductID": 10, - "ProductName": "Ikura", - "SupplierID": 4, - "CategoryID": 8, - "QuantityPerUnit": "12 - 200 ml jars", - "UnitPrice": 31, - "UnitsInStock": 31, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900010" - }, - { - "ProductID": 11, - "ProductName": "Queso Cabrales", - "SupplierID": 5, - "CategoryID": 4, - "QuantityPerUnit": "1 kg pkg.", - "UnitPrice": 21, - "UnitsInStock": 22, - "UnitsOnOrder": 30, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900011" - }, - { - "ProductID": 12, - "ProductName": "Queso Manchego La Pastora", - "SupplierID": 5, - "CategoryID": 4, - "QuantityPerUnit": "10 - 500 g pkgs.", - "UnitPrice": 38, - "UnitsInStock": 86, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900012" - }, - { - "ProductID": 13, - "ProductName": "Konbu", - "SupplierID": 6, - "CategoryID": 8, - "QuantityPerUnit": "2 kg box", - "UnitPrice": 6, - "UnitsInStock": 24, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900013" - }, - { - "ProductID": 14, - "ProductName": "Tofu", - "SupplierID": 6, - "CategoryID": 7, - "QuantityPerUnit": "40 - 100 g pkgs.", - "UnitPrice": 23.25, - "UnitsInStock": 35, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900014" - }, - { - "ProductID": 15, - "ProductName": "Genen Shouyu", - "SupplierID": 6, - "CategoryID": 2, - "QuantityPerUnit": "24 - 250 ml bottles", - "UnitPrice": 15.5, - "UnitsInStock": 39, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900015" - }, - { - "ProductID": 16, - "ProductName": "Pavlova", - "SupplierID": 7, - "CategoryID": 3, - "QuantityPerUnit": "32 - 500 g boxes", - "UnitPrice": 17.45, - "UnitsInStock": 29, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900016" - }, - { - "ProductID": 17, - "ProductName": "Alice Mutton", - "SupplierID": 7, - "CategoryID": 6, - "QuantityPerUnit": "20 - 1 kg tins", - "UnitPrice": 39, - "UnitsInStock": 0, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900017" - }, - { - "ProductID": 18, - "ProductName": "Carnarvon Tigers", - "SupplierID": 7, - "CategoryID": 8, - "QuantityPerUnit": "16 kg pkg.", - "UnitPrice": 62.5, - "UnitsInStock": 42, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900018" - }, - { - "ProductID": 19, - "ProductName": "Teatime Chocolate Biscuits", - "SupplierID": 8, - "CategoryID": 3, - "QuantityPerUnit": "10 boxes x 12 pieces", - "UnitPrice": 9.2, - "UnitsInStock": 25, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900019" - }, - { - "ProductID": 20, - "ProductName": "Sir Rodney's Marmalade", - "SupplierID": 8, - "CategoryID": 3, - "QuantityPerUnit": "30 gift boxes", - "UnitPrice": 81, - "UnitsInStock": 40, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900020" - }, - { - "ProductID": 21, - "ProductName": "Sir Rodney's Scones", - "SupplierID": 8, - "CategoryID": 3, - "QuantityPerUnit": "24 pkgs. x 4 pieces", - "UnitPrice": 10, - "UnitsInStock": 3, - "UnitsOnOrder": 40, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900021" - }, - { - "ProductID": 22, - "ProductName": "Gustaf's Knckebrd", - "SupplierID": 9, - "CategoryID": 5, - "QuantityPerUnit": "24 - 500 g pkgs.", - "UnitPrice": 21, - "UnitsInStock": 104, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900022" - }, - { - "ProductID": 23, - "ProductName": "Tunnbrd", - "SupplierID": 9, - "CategoryID": 5, - "QuantityPerUnit": "12 - 250 g pkgs.", - "UnitPrice": 9, - "UnitsInStock": 61, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900023" - }, - { - "ProductID": 24, - "ProductName": "Guaran Fantstica", - "SupplierID": 10, - "CategoryID": 1, - "QuantityPerUnit": "12 - 355 ml cans", - "UnitPrice": 4.5, - "UnitsInStock": 20, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900024" - }, - { - "ProductID": 25, - "ProductName": "NuNuCa Nu-Nougat-Creme", - "SupplierID": 11, - "CategoryID": 3, - "QuantityPerUnit": "20 - 450 g glasses", - "UnitPrice": 14, - "UnitsInStock": 76, - "UnitsOnOrder": 0, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900025" - }, - { - "ProductID": 26, - "ProductName": "Gumbr Gummibrchen", - "SupplierID": 11, - "CategoryID": 3, - "QuantityPerUnit": "100 - 250 g bags", - "UnitPrice": 31.23, - "UnitsInStock": 15, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900026" - }, - { - "ProductID": 27, - "ProductName": "Schoggi Schokolade", - "SupplierID": 11, - "CategoryID": 3, - "QuantityPerUnit": "100 - 100 g pieces", - "UnitPrice": 43.9, - "UnitsInStock": 49, - "UnitsOnOrder": 0, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900027" - }, - { - "ProductID": 28, - "ProductName": "Rssle Sauerkraut", - "SupplierID": 12, - "CategoryID": 7, - "QuantityPerUnit": "25 - 825 g cans", - "UnitPrice": 45.6, - "UnitsInStock": 26, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900028" - }, - { - "ProductID": 29, - "ProductName": "Thringer Rostbratwurst", - "SupplierID": 12, - "CategoryID": 6, - "QuantityPerUnit": "50 bags x 30 sausgs.", - "UnitPrice": 123.79, - "UnitsInStock": 0, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900029" - }, - { - "ProductID": 30, - "ProductName": "Nord-Ost Matjeshering", - "SupplierID": 13, - "CategoryID": 8, - "QuantityPerUnit": "10 - 200 g glasses", - "UnitPrice": 25.89, - "UnitsInStock": 10, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900030" - }, - { - "ProductID": 31, - "ProductName": "Gorgonzola Telino", - "SupplierID": 14, - "CategoryID": 4, - "QuantityPerUnit": "12 - 100 g pkgs", - "UnitPrice": 12.5, - "UnitsInStock": 0, - "UnitsOnOrder": 70, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900031" - }, - { - "ProductID": 32, - "ProductName": "Mascarpone Fabioli", - "SupplierID": 14, - "CategoryID": 4, - "QuantityPerUnit": "24 - 200 g pkgs.", - "UnitPrice": 32, - "UnitsInStock": 9, - "UnitsOnOrder": 40, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900032" - }, - { - "ProductID": 33, - "ProductName": "Geitost", - "SupplierID": 15, - "CategoryID": 4, - "QuantityPerUnit": "500 g", - "UnitPrice": 2.5, - "UnitsInStock": 112, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900033" - }, - { - "ProductID": 34, - "ProductName": "Sasquatch Ale", - "SupplierID": 16, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 14, - "UnitsInStock": 111, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900034" - }, - { - "ProductID": 35, - "ProductName": "Steeleye Stout", - "SupplierID": 16, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 18, - "UnitsInStock": 20, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900035" - }, - { - "ProductID": 36, - "ProductName": "Inlagd Sill", - "SupplierID": 17, - "CategoryID": 8, - "QuantityPerUnit": "24 - 250 g jars", - "UnitPrice": 19, - "UnitsInStock": 112, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900036" - }, - { - "ProductID": 37, - "ProductName": "Gravad lax", - "SupplierID": 17, - "CategoryID": 8, - "QuantityPerUnit": "12 - 500 g pkgs.", - "UnitPrice": 26, - "UnitsInStock": 11, - "UnitsOnOrder": 50, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900037" - }, - { - "ProductID": 38, - "ProductName": "Cte de Blaye", - "SupplierID": 18, - "CategoryID": 1, - "QuantityPerUnit": "12 - 75 cl bottles", - "UnitPrice": 263.5, - "UnitsInStock": 17, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900038" - }, - { - "ProductID": 39, - "ProductName": "Chartreuse verte", - "SupplierID": 18, - "CategoryID": 1, - "QuantityPerUnit": "750 cc per bottle", - "UnitPrice": 18, - "UnitsInStock": 69, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900039" - }, - { - "ProductID": 40, - "ProductName": "Boston Crab Meat", - "SupplierID": 19, - "CategoryID": 8, - "QuantityPerUnit": "24 - 4 oz tins", - "UnitPrice": 18.4, - "UnitsInStock": 123, - "UnitsOnOrder": 0, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900040" - }, - { - "ProductID": 41, - "ProductName": "Jack's New England Clam Chowder", - "SupplierID": 19, - "CategoryID": 8, - "QuantityPerUnit": "12 - 12 oz cans", - "UnitPrice": 9.65, - "UnitsInStock": 85, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900041" - }, - { - "ProductID": 42, - "ProductName": "Singaporean Hokkien Fried Mee", - "SupplierID": 20, - "CategoryID": 5, - "QuantityPerUnit": "32 - 1 kg pkgs.", - "UnitPrice": 14, - "UnitsInStock": 26, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900042" - }, - { - "ProductID": 43, - "ProductName": "Ipoh Coffee", - "SupplierID": 20, - "CategoryID": 1, - "QuantityPerUnit": "16 - 500 g tins", - "UnitPrice": 46, - "UnitsInStock": 17, - "UnitsOnOrder": 10, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900043" - }, - { - "ProductID": 44, - "ProductName": "Gula Malacca", - "SupplierID": 20, - "CategoryID": 2, - "QuantityPerUnit": "20 - 2 kg bags", - "UnitPrice": 19.45, - "UnitsInStock": 27, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900044" - }, - { - "ProductID": 45, - "ProductName": "Rogede sild", - "SupplierID": 21, - "CategoryID": 8, - "QuantityPerUnit": "1k pkg.", - "UnitPrice": 9.5, - "UnitsInStock": 5, - "UnitsOnOrder": 70, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900045" - }, - { - "ProductID": 46, - "ProductName": "Spegesild", - "SupplierID": 21, - "CategoryID": 8, - "QuantityPerUnit": "4 - 450 g glasses", - "UnitPrice": 12, - "UnitsInStock": 95, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900046" - }, - { - "ProductID": 47, - "ProductName": "Zaanse koeken", - "SupplierID": 22, - "CategoryID": 3, - "QuantityPerUnit": "10 - 4 oz boxes", - "UnitPrice": 9.5, - "UnitsInStock": 36, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900047" - }, - { - "ProductID": 48, - "ProductName": "Chocolade", - "SupplierID": 22, - "CategoryID": 3, - "QuantityPerUnit": "10 pkgs.", - "UnitPrice": 12.75, - "UnitsInStock": 15, - "UnitsOnOrder": 70, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900048" - }, - { - "ProductID": 49, - "ProductName": "Maxilaku", - "SupplierID": 23, - "CategoryID": 3, - "QuantityPerUnit": "24 - 50 g pkgs.", - "UnitPrice": 20, - "UnitsInStock": 10, - "UnitsOnOrder": 60, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900049" - }, - { - "ProductID": 50, - "ProductName": "Valkoinen suklaa", - "SupplierID": 23, - "CategoryID": 3, - "QuantityPerUnit": "12 - 100 g bars", - "UnitPrice": 16.25, - "UnitsInStock": 65, - "UnitsOnOrder": 0, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900050" - }, - { - "ProductID": 51, - "ProductName": "Manjimup Dried Apples", - "SupplierID": 24, - "CategoryID": 7, - "QuantityPerUnit": "50 - 300 g pkgs.", - "UnitPrice": 53, - "UnitsInStock": 20, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900051" - }, - { - "ProductID": 52, - "ProductName": "Filo Mix", - "SupplierID": 24, - "CategoryID": 5, - "QuantityPerUnit": "16 - 2 kg boxes", - "UnitPrice": 7, - "UnitsInStock": 38, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900052" - }, - { - "ProductID": 53, - "ProductName": "Perth Pasties", - "SupplierID": 24, - "CategoryID": 6, - "QuantityPerUnit": "48 pieces", - "UnitPrice": 32.8, - "UnitsInStock": 0, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900053" - }, - { - "ProductID": 54, - "ProductName": "Tourtire", - "SupplierID": 25, - "CategoryID": 6, - "QuantityPerUnit": "16 pies", - "UnitPrice": 7.45, - "UnitsInStock": 21, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900054" - }, - { - "ProductID": 55, - "ProductName": "Pt chinois", - "SupplierID": 25, - "CategoryID": 6, - "QuantityPerUnit": "24 boxes x 2 pies", - "UnitPrice": 24, - "UnitsInStock": 115, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900055" - }, - { - "ProductID": 56, - "ProductName": "Gnocchi di nonna Alice", - "SupplierID": 26, - "CategoryID": 5, - "QuantityPerUnit": "24 - 250 g pkgs.", - "UnitPrice": 38, - "UnitsInStock": 21, - "UnitsOnOrder": 10, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900056" - }, - { - "ProductID": 57, - "ProductName": "Ravioli Angelo", - "SupplierID": 26, - "CategoryID": 5, - "QuantityPerUnit": "24 - 250 g pkgs.", - "UnitPrice": 19.5, - "UnitsInStock": 36, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900057" - }, - { - "ProductID": 58, - "ProductName": "Escargots de Bourgogne", - "SupplierID": 27, - "CategoryID": 8, - "QuantityPerUnit": "24 pieces", - "UnitPrice": 13.25, - "UnitsInStock": 62, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900058" - }, - { - "ProductID": 59, - "ProductName": "Raclette Courdavault", - "SupplierID": 28, - "CategoryID": 4, - "QuantityPerUnit": "5 kg pkg.", - "UnitPrice": 55, - "UnitsInStock": 79, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900059" - }, - { - "ProductID": 60, - "ProductName": "Camembert Pierrot", - "SupplierID": 28, - "CategoryID": 4, - "QuantityPerUnit": "15 - 300 g rounds", - "UnitPrice": 34, - "UnitsInStock": 19, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900060" - }, - { - "ProductID": 61, - "ProductName": "Sirop d'rable", - "SupplierID": 29, - "CategoryID": 2, - "QuantityPerUnit": "24 - 500 ml bottles", - "UnitPrice": 28.5, - "UnitsInStock": 113, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900061" - }, - { - "ProductID": 62, - "ProductName": "Tarte au sucre", - "SupplierID": 29, - "CategoryID": 3, - "QuantityPerUnit": "48 pies", - "UnitPrice": 49.3, - "UnitsInStock": 17, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900062" - }, - { - "ProductID": 63, - "ProductName": "Vegie-spread", - "SupplierID": 7, - "CategoryID": 2, - "QuantityPerUnit": "15 - 625 g jars", - "UnitPrice": 43.9, - "UnitsInStock": 24, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900063" - }, - { - "ProductID": 64, - "ProductName": "Wimmers gute Semmelkndel", - "SupplierID": 12, - "CategoryID": 5, - "QuantityPerUnit": "20 bags x 4 pieces", - "UnitPrice": 33.25, - "UnitsInStock": 22, - "UnitsOnOrder": 80, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900064" - }, - { - "ProductID": 65, - "ProductName": "Louisiana Fiery Hot Pepper Sauce", - "SupplierID": 2, - "CategoryID": 2, - "QuantityPerUnit": "32 - 8 oz bottles", - "UnitPrice": 21.05, - "UnitsInStock": 76, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900065" - }, - { - "ProductID": 66, - "ProductName": "Louisiana Hot Spiced Okra", - "SupplierID": 2, - "CategoryID": 2, - "QuantityPerUnit": "24 - 8 oz jars", - "UnitPrice": 17, - "UnitsInStock": 4, - "UnitsOnOrder": 100, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900066" - }, - { - "ProductID": 67, - "ProductName": "Laughing Lumberjack Lager", - "SupplierID": 16, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 14, - "UnitsInStock": 52, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900067" - }, - { - "ProductID": 68, - "ProductName": "Scottish Longbreads", - "SupplierID": 8, - "CategoryID": 3, - "QuantityPerUnit": "10 boxes x 8 pieces", - "UnitPrice": 12.5, - "UnitsInStock": 6, - "UnitsOnOrder": 10, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900068" - }, - { - "ProductID": 69, - "ProductName": "Gudbrandsdalsost", - "SupplierID": 15, - "CategoryID": 4, - "QuantityPerUnit": "10 kg pkg.", - "UnitPrice": 36, - "UnitsInStock": 26, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900069" - }, - { - "ProductID": 70, - "ProductName": "Outback Lager", - "SupplierID": 7, - "CategoryID": 1, - "QuantityPerUnit": "24 - 355 ml bottles", - "UnitPrice": 15, - "UnitsInStock": 15, - "UnitsOnOrder": 10, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900070" - }, - { - "ProductID": 71, - "ProductName": "Flotemysost", - "SupplierID": 15, - "CategoryID": 4, - "QuantityPerUnit": "10 - 500 g pkgs.", - "UnitPrice": 21.5, - "UnitsInStock": 26, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900071" - }, - { - "ProductID": 72, - "ProductName": "Mozzarella di Giovanni", - "SupplierID": 14, - "CategoryID": 4, - "QuantityPerUnit": "24 - 200 g pkgs.", - "UnitPrice": 34.8, - "UnitsInStock": 14, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900072" - }, - { - "ProductID": 73, - "ProductName": "Rd Kaviar", - "SupplierID": 17, - "CategoryID": 8, - "QuantityPerUnit": "24 - 150 g jars", - "UnitPrice": 15, - "UnitsInStock": 101, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900073" - }, - { - "ProductID": 74, - "ProductName": "Longlife Tofu", - "SupplierID": 4, - "CategoryID": 7, - "QuantityPerUnit": "5 kg pkg.", - "UnitPrice": 10, - "UnitsInStock": 4, - "UnitsOnOrder": 20, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900074" - }, - { - "ProductID": 75, - "ProductName": "Rhnbru Klosterbier", - "SupplierID": 12, - "CategoryID": 1, - "QuantityPerUnit": "24 - 0.5 l bottles", - "UnitPrice": 7.75, - "UnitsInStock": 125, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900075" - }, - { - "ProductID": 76, - "ProductName": "Lakkalikri", - "SupplierID": 23, - "CategoryID": 1, - "QuantityPerUnit": "500 ml", - "UnitPrice": 18, - "UnitsInStock": 57, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900076" - }, - { - "ProductID": 77, - "ProductName": "Original Frankfurter grne Soe", - "SupplierID": 12, - "CategoryID": 2, - "QuantityPerUnit": "12 boxes", - "UnitPrice": 13, - "UnitsInStock": 32, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900077" - } - ] -} +{ + "Products": [ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": 18, + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900001" + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 19, + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900002" + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": 10, + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900003" + }, + { + "ProductID": 4, + "ProductName": "Chef Anton's Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": 22, + "UnitsInStock": 53, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900004" + }, + { + "ProductID": 5, + "ProductName": "Chef Anton's Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": 21.35, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900005" + }, + { + "ProductID": 6, + "ProductName": "Grandma's Boysenberry Spread", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 8 oz jars", + "UnitPrice": 25, + "UnitsInStock": 120, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900006" + }, + { + "ProductID": 7, + "ProductName": "Uncle Bob's Organic Dried Pears", + "SupplierID": 3, + "CategoryID": 7, + "QuantityPerUnit": "12 - 1 lb pkgs.", + "UnitPrice": 30, + "UnitsInStock": 15, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900007" + }, + { + "ProductID": 8, + "ProductName": "Northwoods Cranberry Sauce", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 12 oz jars", + "UnitPrice": 40, + "UnitsInStock": 6, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900008" + }, + { + "ProductID": 9, + "ProductName": "Mishi Kobe Niku", + "SupplierID": 4, + "CategoryID": 6, + "QuantityPerUnit": "18 - 500 g pkgs.", + "UnitPrice": 97, + "UnitsInStock": 29, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900009" + }, + { + "ProductID": 10, + "ProductName": "Ikura", + "SupplierID": 4, + "CategoryID": 8, + "QuantityPerUnit": "12 - 200 ml jars", + "UnitPrice": 31, + "UnitsInStock": 31, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900010" + }, + { + "ProductID": 11, + "ProductName": "Queso Cabrales", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "1 kg pkg.", + "UnitPrice": 21, + "UnitsInStock": 22, + "UnitsOnOrder": 30, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900011" + }, + { + "ProductID": 12, + "ProductName": "Queso Manchego La Pastora", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 86, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900012" + }, + { + "ProductID": 13, + "ProductName": "Konbu", + "SupplierID": 6, + "CategoryID": 8, + "QuantityPerUnit": "2 kg box", + "UnitPrice": 6, + "UnitsInStock": 24, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900013" + }, + { + "ProductID": 14, + "ProductName": "Tofu", + "SupplierID": 6, + "CategoryID": 7, + "QuantityPerUnit": "40 - 100 g pkgs.", + "UnitPrice": 23.25, + "UnitsInStock": 35, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900014" + }, + { + "ProductID": 15, + "ProductName": "Genen Shouyu", + "SupplierID": 6, + "CategoryID": 2, + "QuantityPerUnit": "24 - 250 ml bottles", + "UnitPrice": 15.5, + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900015" + }, + { + "ProductID": 16, + "ProductName": "Pavlova", + "SupplierID": 7, + "CategoryID": 3, + "QuantityPerUnit": "32 - 500 g boxes", + "UnitPrice": 17.45, + "UnitsInStock": 29, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900016" + }, + { + "ProductID": 17, + "ProductName": "Alice Mutton", + "SupplierID": 7, + "CategoryID": 6, + "QuantityPerUnit": "20 - 1 kg tins", + "UnitPrice": 39, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900017" + }, + { + "ProductID": 18, + "ProductName": "Carnarvon Tigers", + "SupplierID": 7, + "CategoryID": 8, + "QuantityPerUnit": "16 kg pkg.", + "UnitPrice": 62.5, + "UnitsInStock": 42, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900018" + }, + { + "ProductID": 19, + "ProductName": "Teatime Chocolate Biscuits", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "10 boxes x 12 pieces", + "UnitPrice": 9.2, + "UnitsInStock": 25, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900019" + }, + { + "ProductID": 20, + "ProductName": "Sir Rodney's Marmalade", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "30 gift boxes", + "UnitPrice": 81, + "UnitsInStock": 40, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900020" + }, + { + "ProductID": 21, + "ProductName": "Sir Rodney's Scones", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "24 pkgs. x 4 pieces", + "UnitPrice": 10, + "UnitsInStock": 3, + "UnitsOnOrder": 40, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900021" + }, + { + "ProductID": 22, + "ProductName": "Gustaf's Knckebrd", + "SupplierID": 9, + "CategoryID": 5, + "QuantityPerUnit": "24 - 500 g pkgs.", + "UnitPrice": 21, + "UnitsInStock": 104, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900022" + }, + { + "ProductID": 23, + "ProductName": "Tunnbrd", + "SupplierID": 9, + "CategoryID": 5, + "QuantityPerUnit": "12 - 250 g pkgs.", + "UnitPrice": 9, + "UnitsInStock": 61, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900023" + }, + { + "ProductID": 24, + "ProductName": "Guaran Fantstica", + "SupplierID": 10, + "CategoryID": 1, + "QuantityPerUnit": "12 - 355 ml cans", + "UnitPrice": 4.5, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900024" + }, + { + "ProductID": 25, + "ProductName": "NuNuCa Nu-Nougat-Creme", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "20 - 450 g glasses", + "UnitPrice": 14, + "UnitsInStock": 76, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900025" + }, + { + "ProductID": 26, + "ProductName": "Gumbr Gummibrchen", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "100 - 250 g bags", + "UnitPrice": 31.23, + "UnitsInStock": 15, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900026" + }, + { + "ProductID": 27, + "ProductName": "Schoggi Schokolade", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "100 - 100 g pieces", + "UnitPrice": 43.9, + "UnitsInStock": 49, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900027" + }, + { + "ProductID": 28, + "ProductName": "Rssle Sauerkraut", + "SupplierID": 12, + "CategoryID": 7, + "QuantityPerUnit": "25 - 825 g cans", + "UnitPrice": 45.6, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900028" + }, + { + "ProductID": 29, + "ProductName": "Thringer Rostbratwurst", + "SupplierID": 12, + "CategoryID": 6, + "QuantityPerUnit": "50 bags x 30 sausgs.", + "UnitPrice": 123.79, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900029" + }, + { + "ProductID": 30, + "ProductName": "Nord-Ost Matjeshering", + "SupplierID": 13, + "CategoryID": 8, + "QuantityPerUnit": "10 - 200 g glasses", + "UnitPrice": 25.89, + "UnitsInStock": 10, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900030" + }, + { + "ProductID": 31, + "ProductName": "Gorgonzola Telino", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "12 - 100 g pkgs", + "UnitPrice": 12.5, + "UnitsInStock": 0, + "UnitsOnOrder": 70, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900031" + }, + { + "ProductID": 32, + "ProductName": "Mascarpone Fabioli", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "24 - 200 g pkgs.", + "UnitPrice": 32, + "UnitsInStock": 9, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900032" + }, + { + "ProductID": 33, + "ProductName": "Geitost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "500 g", + "UnitPrice": 2.5, + "UnitsInStock": 112, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900033" + }, + { + "ProductID": 34, + "ProductName": "Sasquatch Ale", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 14, + "UnitsInStock": 111, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900034" + }, + { + "ProductID": 35, + "ProductName": "Steeleye Stout", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 18, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900035" + }, + { + "ProductID": 36, + "ProductName": "Inlagd Sill", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "24 - 250 g jars", + "UnitPrice": 19, + "UnitsInStock": 112, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900036" + }, + { + "ProductID": 37, + "ProductName": "Gravad lax", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "12 - 500 g pkgs.", + "UnitPrice": 26, + "UnitsInStock": 11, + "UnitsOnOrder": 50, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900037" + }, + { + "ProductID": 38, + "ProductName": "Cte de Blaye", + "SupplierID": 18, + "CategoryID": 1, + "QuantityPerUnit": "12 - 75 cl bottles", + "UnitPrice": 263.5, + "UnitsInStock": 17, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900038" + }, + { + "ProductID": 39, + "ProductName": "Chartreuse verte", + "SupplierID": 18, + "CategoryID": 1, + "QuantityPerUnit": "750 cc per bottle", + "UnitPrice": 18, + "UnitsInStock": 69, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900039" + }, + { + "ProductID": 40, + "ProductName": "Boston Crab Meat", + "SupplierID": 19, + "CategoryID": 8, + "QuantityPerUnit": "24 - 4 oz tins", + "UnitPrice": 18.4, + "UnitsInStock": 123, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900040" + }, + { + "ProductID": 41, + "ProductName": "Jack's New England Clam Chowder", + "SupplierID": 19, + "CategoryID": 8, + "QuantityPerUnit": "12 - 12 oz cans", + "UnitPrice": 9.65, + "UnitsInStock": 85, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900041" + }, + { + "ProductID": 42, + "ProductName": "Singaporean Hokkien Fried Mee", + "SupplierID": 20, + "CategoryID": 5, + "QuantityPerUnit": "32 - 1 kg pkgs.", + "UnitPrice": 14, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900042" + }, + { + "ProductID": 43, + "ProductName": "Ipoh Coffee", + "SupplierID": 20, + "CategoryID": 1, + "QuantityPerUnit": "16 - 500 g tins", + "UnitPrice": 46, + "UnitsInStock": 17, + "UnitsOnOrder": 10, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900043" + }, + { + "ProductID": 44, + "ProductName": "Gula Malacca", + "SupplierID": 20, + "CategoryID": 2, + "QuantityPerUnit": "20 - 2 kg bags", + "UnitPrice": 19.45, + "UnitsInStock": 27, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900044" + }, + { + "ProductID": 45, + "ProductName": "Rogede sild", + "SupplierID": 21, + "CategoryID": 8, + "QuantityPerUnit": "1k pkg.", + "UnitPrice": 9.5, + "UnitsInStock": 5, + "UnitsOnOrder": 70, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900045" + }, + { + "ProductID": 46, + "ProductName": "Spegesild", + "SupplierID": 21, + "CategoryID": 8, + "QuantityPerUnit": "4 - 450 g glasses", + "UnitPrice": 12, + "UnitsInStock": 95, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900046" + }, + { + "ProductID": 47, + "ProductName": "Zaanse koeken", + "SupplierID": 22, + "CategoryID": 3, + "QuantityPerUnit": "10 - 4 oz boxes", + "UnitPrice": 9.5, + "UnitsInStock": 36, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900047" + }, + { + "ProductID": 48, + "ProductName": "Chocolade", + "SupplierID": 22, + "CategoryID": 3, + "QuantityPerUnit": "10 pkgs.", + "UnitPrice": 12.75, + "UnitsInStock": 15, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900048" + }, + { + "ProductID": 49, + "ProductName": "Maxilaku", + "SupplierID": 23, + "CategoryID": 3, + "QuantityPerUnit": "24 - 50 g pkgs.", + "UnitPrice": 20, + "UnitsInStock": 10, + "UnitsOnOrder": 60, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900049" + }, + { + "ProductID": 50, + "ProductName": "Valkoinen suklaa", + "SupplierID": 23, + "CategoryID": 3, + "QuantityPerUnit": "12 - 100 g bars", + "UnitPrice": 16.25, + "UnitsInStock": 65, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900050" + }, + { + "ProductID": 51, + "ProductName": "Manjimup Dried Apples", + "SupplierID": 24, + "CategoryID": 7, + "QuantityPerUnit": "50 - 300 g pkgs.", + "UnitPrice": 53, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900051" + }, + { + "ProductID": 52, + "ProductName": "Filo Mix", + "SupplierID": 24, + "CategoryID": 5, + "QuantityPerUnit": "16 - 2 kg boxes", + "UnitPrice": 7, + "UnitsInStock": 38, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900052" + }, + { + "ProductID": 53, + "ProductName": "Perth Pasties", + "SupplierID": 24, + "CategoryID": 6, + "QuantityPerUnit": "48 pieces", + "UnitPrice": 32.8, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900053" + }, + { + "ProductID": 54, + "ProductName": "Tourtire", + "SupplierID": 25, + "CategoryID": 6, + "QuantityPerUnit": "16 pies", + "UnitPrice": 7.45, + "UnitsInStock": 21, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900054" + }, + { + "ProductID": 55, + "ProductName": "Pt chinois", + "SupplierID": 25, + "CategoryID": 6, + "QuantityPerUnit": "24 boxes x 2 pies", + "UnitPrice": 24, + "UnitsInStock": 115, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900055" + }, + { + "ProductID": 56, + "ProductName": "Gnocchi di nonna Alice", + "SupplierID": 26, + "CategoryID": 5, + "QuantityPerUnit": "24 - 250 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 21, + "UnitsOnOrder": 10, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900056" + }, + { + "ProductID": 57, + "ProductName": "Ravioli Angelo", + "SupplierID": 26, + "CategoryID": 5, + "QuantityPerUnit": "24 - 250 g pkgs.", + "UnitPrice": 19.5, + "UnitsInStock": 36, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900057" + }, + { + "ProductID": 58, + "ProductName": "Escargots de Bourgogne", + "SupplierID": 27, + "CategoryID": 8, + "QuantityPerUnit": "24 pieces", + "UnitPrice": 13.25, + "UnitsInStock": 62, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900058" + }, + { + "ProductID": 59, + "ProductName": "Raclette Courdavault", + "SupplierID": 28, + "CategoryID": 4, + "QuantityPerUnit": "5 kg pkg.", + "UnitPrice": 55, + "UnitsInStock": 79, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900059" + }, + { + "ProductID": 60, + "ProductName": "Camembert Pierrot", + "SupplierID": 28, + "CategoryID": 4, + "QuantityPerUnit": "15 - 300 g rounds", + "UnitPrice": 34, + "UnitsInStock": 19, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900060" + }, + { + "ProductID": 61, + "ProductName": "Sirop d'rable", + "SupplierID": 29, + "CategoryID": 2, + "QuantityPerUnit": "24 - 500 ml bottles", + "UnitPrice": 28.5, + "UnitsInStock": 113, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900061" + }, + { + "ProductID": 62, + "ProductName": "Tarte au sucre", + "SupplierID": 29, + "CategoryID": 3, + "QuantityPerUnit": "48 pies", + "UnitPrice": 49.3, + "UnitsInStock": 17, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900062" + }, + { + "ProductID": 63, + "ProductName": "Vegie-spread", + "SupplierID": 7, + "CategoryID": 2, + "QuantityPerUnit": "15 - 625 g jars", + "UnitPrice": 43.9, + "UnitsInStock": 24, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900063" + }, + { + "ProductID": 64, + "ProductName": "Wimmers gute Semmelkndel", + "SupplierID": 12, + "CategoryID": 5, + "QuantityPerUnit": "20 bags x 4 pieces", + "UnitPrice": 33.25, + "UnitsInStock": 22, + "UnitsOnOrder": 80, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900064" + }, + { + "ProductID": 65, + "ProductName": "Louisiana Fiery Hot Pepper Sauce", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "32 - 8 oz bottles", + "UnitPrice": 21.05, + "UnitsInStock": 76, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900065" + }, + { + "ProductID": 66, + "ProductName": "Louisiana Hot Spiced Okra", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "24 - 8 oz jars", + "UnitPrice": 17, + "UnitsInStock": 4, + "UnitsOnOrder": 100, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900066" + }, + { + "ProductID": 67, + "ProductName": "Laughing Lumberjack Lager", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 14, + "UnitsInStock": 52, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900067" + }, + { + "ProductID": 68, + "ProductName": "Scottish Longbreads", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "10 boxes x 8 pieces", + "UnitPrice": 12.5, + "UnitsInStock": 6, + "UnitsOnOrder": 10, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900068" + }, + { + "ProductID": 69, + "ProductName": "Gudbrandsdalsost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "10 kg pkg.", + "UnitPrice": 36, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900069" + }, + { + "ProductID": 70, + "ProductName": "Outback Lager", + "SupplierID": 7, + "CategoryID": 1, + "QuantityPerUnit": "24 - 355 ml bottles", + "UnitPrice": 15, + "UnitsInStock": 15, + "UnitsOnOrder": 10, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900070" + }, + { + "ProductID": 71, + "ProductName": "Flotemysost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 21.5, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900071" + }, + { + "ProductID": 72, + "ProductName": "Mozzarella di Giovanni", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "24 - 200 g pkgs.", + "UnitPrice": 34.8, + "UnitsInStock": 14, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900072" + }, + { + "ProductID": 73, + "ProductName": "Rd Kaviar", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "24 - 150 g jars", + "UnitPrice": 15, + "UnitsInStock": 101, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900073" + }, + { + "ProductID": 74, + "ProductName": "Longlife Tofu", + "SupplierID": 4, + "CategoryID": 7, + "QuantityPerUnit": "5 kg pkg.", + "UnitPrice": 10, + "UnitsInStock": 4, + "UnitsOnOrder": 20, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900074" + }, + { + "ProductID": 75, + "ProductName": "Rhnbru Klosterbier", + "SupplierID": 12, + "CategoryID": 1, + "QuantityPerUnit": "24 - 0.5 l bottles", + "UnitPrice": 7.75, + "UnitsInStock": 125, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900075" + }, + { + "ProductID": 76, + "ProductName": "Lakkalikri", + "SupplierID": 23, + "CategoryID": 1, + "QuantityPerUnit": "500 ml", + "UnitPrice": 18, + "UnitsInStock": 57, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900076" + }, + { + "ProductID": 77, + "ProductName": "Original Frankfurter grne Soe", + "SupplierID": 12, + "CategoryID": 2, + "QuantityPerUnit": "12 boxes", + "UnitPrice": 13, + "UnitsInStock": 32, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900077" + } + ] +} diff --git a/AspNetCore.Reporting.Angular/Models/ApplicationUser.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Models/ApplicationUser.cs similarity index 57% rename from AspNetCore.Reporting.Angular/Models/ApplicationUser.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Models/ApplicationUser.cs index 79d0434..cfac1f6 100644 --- a/AspNetCore.Reporting.Angular/Models/ApplicationUser.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Models/ApplicationUser.cs @@ -1,10 +1,6 @@ -using Microsoft.AspNetCore.Identity; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace AspNetCore.Reporting.Angular.Models { - public class ApplicationUser : IdentityUser { - } -} +using Microsoft.AspNetCore.Identity; + +namespace AspNetCore.Reporting.Angular.Models { + public class ApplicationUser : IdentityUser { + } +} diff --git a/AspNetCore.Reporting.Angular/Pages/Error.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Error.cshtml similarity index 97% rename from AspNetCore.Reporting.Angular/Pages/Error.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Error.cshtml index 09da0d2..6f92b95 100644 --- a/AspNetCore.Reporting.Angular/Pages/Error.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Error.cshtml @@ -1,26 +1,26 @@ -@page -@model ErrorModel -@{ - ViewData["Title"] = "Error"; -} - -

Error.

-

An error occurred while processing your request.

- -@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} - -

Development Mode

-

- Swapping to the Development environment displays detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

+@page +@model ErrorModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to the Development environment displays detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

diff --git a/AspNetCore.Reporting.Angular/Pages/Error.cshtml.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Error.cshtml.cs similarity index 96% rename from AspNetCore.Reporting.Angular/Pages/Error.cshtml.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Error.cshtml.cs index 115cb4c..aa97e05 100644 --- a/AspNetCore.Reporting.Angular/Pages/Error.cshtml.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Error.cshtml.cs @@ -1,27 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Logging; - -namespace AspNetCore.Reporting.Angular.Pages { - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public class ErrorModel : PageModel { - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) { - _logger = logger; - } - - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - public void OnGet() { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace AspNetCore.Reporting.Angular.Pages { + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public class ErrorModel : PageModel { + private readonly ILogger _logger; + + public ErrorModel(ILogger logger) { + _logger = logger; + } + + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + public void OnGet() { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} diff --git a/AspNetCore.Reporting.Angular/Pages/Shared/_Layout.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_Layout.cshtml similarity index 97% rename from AspNetCore.Reporting.Angular/Pages/Shared/_Layout.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_Layout.cshtml index e654b23..911ef51 100644 --- a/AspNetCore.Reporting.Angular/Pages/Shared/_Layout.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_Layout.cshtml @@ -1,57 +1,57 @@ -@using Microsoft.AspNetCore.Hosting -@using Microsoft.AspNetCore.Mvc.ViewEngines -@inject IWebHostEnvironment Environment -@inject ICompositeViewEngine Engine - - - - - - @ViewData["Title"] - AspNetCore.Reporting.Angular - - - - -
- -
- -
-
- @RenderBody() -
-
-
-
- © 2020 - AspNetCore.Reporting.Angular - Privacy -
-
- - - - @RenderSection("Scripts", required: false) - - +@using Microsoft.AspNetCore.Hosting +@using Microsoft.AspNetCore.Mvc.ViewEngines +@inject IWebHostEnvironment Environment +@inject ICompositeViewEngine Engine + + + + + + @ViewData["Title"] - AspNetCore.Reporting.Angular + + + + +
+ +
+ +
+
+ @RenderBody() +
+
+
+
+ © 2020 - AspNetCore.Reporting.Angular - Privacy +
+
+ + + + @RenderSection("Scripts", required: false) + + diff --git a/AspNetCore.Reporting.Angular/Pages/Shared/_LoginPartial.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_LoginPartial.cshtml similarity index 97% rename from AspNetCore.Reporting.Angular/Pages/Shared/_LoginPartial.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_LoginPartial.cshtml index 3dd235f..b07ee21 100644 --- a/AspNetCore.Reporting.Angular/Pages/Shared/_LoginPartial.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_LoginPartial.cshtml @@ -1,37 +1,37 @@ -@using Microsoft.AspNetCore.Identity -@using AspNetCore.Reporting.Angular.Models; -@using AspNetCore.Reporting.Common.Data; -@inject SignInManager SignInManager -@inject UserManager UserManager - -@{ - string returnUrl = null; - var query = ViewContext.HttpContext.Request.Query; - if (query.ContainsKey("returnUrl")) - { - returnUrl = query["returnUrl"]; - } -} - - +@using Microsoft.AspNetCore.Identity +@using AspNetCore.Reporting.Angular.Models; +@using AspNetCore.Reporting.Common.Data; +@inject SignInManager SignInManager +@inject UserManager UserManager + +@{ + string returnUrl = null; + var query = ViewContext.HttpContext.Request.Query; + if (query.ContainsKey("returnUrl")) + { + returnUrl = query["returnUrl"]; + } +} + + diff --git a/AspNetCore.Reporting.Angular/Pages/Shared/_ValidationScriptsPartial.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_ValidationScriptsPartial.cshtml similarity index 98% rename from AspNetCore.Reporting.Angular/Pages/Shared/_ValidationScriptsPartial.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_ValidationScriptsPartial.cshtml index 6d138d2..bacc0ae 100644 --- a/AspNetCore.Reporting.Angular/Pages/Shared/_ValidationScriptsPartial.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/Shared/_ValidationScriptsPartial.cshtml @@ -1,18 +1,18 @@ - - - - - - - - + + + + + + + + diff --git a/AspNetCore.Reporting.Angular/Pages/_ViewImports.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/_ViewImports.cshtml similarity index 97% rename from AspNetCore.Reporting.Angular/Pages/_ViewImports.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/_ViewImports.cshtml index c6070fb..2396521 100644 --- a/AspNetCore.Reporting.Angular/Pages/_ViewImports.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/_ViewImports.cshtml @@ -1,3 +1,3 @@ -@using AspNetCore.Reporting.Angular -@namespace AspNetCore.Reporting.Angular.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@using AspNetCore.Reporting.Angular +@namespace AspNetCore.Reporting.Angular.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/AspNetCore.Reporting.Angular/Pages/_ViewStart.cshtml b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/_ViewStart.cshtml similarity index 91% rename from AspNetCore.Reporting.Angular/Pages/_ViewStart.cshtml rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/_ViewStart.cshtml index 6e88aa3..a5f1004 100644 --- a/AspNetCore.Reporting.Angular/Pages/_ViewStart.cshtml +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Pages/_ViewStart.cshtml @@ -1,3 +1,3 @@ -@{ - Layout = "_Layout"; -} +@{ + Layout = "_Layout"; +} diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Program.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Program.cs new file mode 100644 index 0000000..e17f694 --- /dev/null +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Program.cs @@ -0,0 +1,101 @@ +using AspNetCore.Reporting.Angular.Data; +using AspNetCore.Reporting.Common.Data; +using AspNetCore.Reporting.Common.Reports; +using AspNetCore.Reporting.Common.Services; +using AspNetCore.Reporting.Common.Services.Reporting; +using DevExpress.AspNetCore; +using DevExpress.AspNetCore.Reporting; +using DevExpress.Utils; +using DevExpress.XtraReports.Web.Extensions; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +AppDomain.CurrentDomain.SetData("DataDirectory", builder.Environment.ContentRootPath); +builder.Services.AddDevExpressControls(); +builder.Services.AddDbContext(options => + //.UseSqlServer(builder.Configuration.GetConnectionString("DefaultMSSqlConnection"))); + options.UseSqlite(builder.Configuration.GetConnectionString("DefaultSqliteConnection"))); + +builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddUserManager>() + .AddEntityFrameworkStores(); +builder.Services.AddIdentityServer() +.AddApiAuthorization(options => { + var api = options.ApiResources.FirstOrDefault(); + api.UserClaims = new[] { System.Security.Claims.ClaimTypes.Sid }; +}); + +builder.Services.AddAuthentication().AddIdentityServerJwt(); +builder.Services.AddControllersWithViews().AddNewtonsoftJson(); +builder.Services.AddRazorPages(); +builder.Services.AddCors(options => { + options.AddPolicy("AllowFrontendDev", policy => { + policy.WithOrigins(builder.Configuration.GetSection("IdentityServer:Clients:AspNetCore.Reporting.Angular.Client:AllowedCorsOrigins").Get()) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); +}); +builder.Services.ConfigureReportingServices(x => { + if(builder.Environment.IsDevelopment()) { + x.UseDevelopmentMode(); + } + x.ConfigureReportDesigner(reportDesignerConfigurator => { + reportDesignerConfigurator.RegisterObjectDataSourceWizardTypeProvider(); + }); +}); +ServiceRegistrator.AddCommonServices(builder.Services, builder.Environment.ContentRootPath); + +builder.Services.AddSingleton, ScopedDbContextProvider>(); +builder.Services.AddScoped>(); +builder.Services.AddTransient>(); +builder.Services.AddTransient(); +DeserializationSettings.RegisterTrustedClass(typeof(CourseListReportRepository)); +builder.Services.AddTransient(); +DeserializationSettings.RegisterTrustedClass(typeof(MyEnrollmentsReportRepository)); + +var app = builder.Build(); +using(var scope = app.Services.CreateScope()) { + var services = scope.ServiceProvider; + try { + var context = services.GetRequiredService(); + var userManager = services.GetRequiredService>(); + DbInitializer.Initialize(context, userManager, new ReportsFactory()); + } catch(Exception exception) { + var logger = services.GetRequiredService>(); + logger.LogError(exception, "An error occurred while seeding the database."); + } +} +app.UseDevExpressControls(); +if(app.Environment.IsDevelopment()) { + app.UseDeveloperExceptionPage(); +} else { + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +//var contentDirectoryAllowRule = DirectoryAccessRule.Allow(new DirectoryInfo(Path.Combine(app.Environment.ContentRootPath, "Content")).FullName); +//AccessSettings.ReportingSpecificResources.SetRules(contentDirectoryAllowRule, UrlAccessRule.Allow()); + +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseCors("AllowFrontendDev"); +app.UseRouting(); +app.UseDevExpressControls(); +app.UseAuthentication(); +app.UseIdentityServer(); +app.UseAuthorization(); + +System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12; +app.MapRazorPages(); +app.MapControllerRoute( + name: "default", + pattern: "{controller}/{action=Index}/{id?}"); + +app.MapFallbackToFile("/index.html"); + +app.Run(); \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/Properties/serviceDependencies.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Properties/serviceDependencies.json similarity index 94% rename from AspNetCore.Reporting.Angular/Properties/serviceDependencies.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Properties/serviceDependencies.json index 79b8073..224ac68 100644 --- a/AspNetCore.Reporting.Angular/Properties/serviceDependencies.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Properties/serviceDependencies.json @@ -1,8 +1,8 @@ -{ - "dependencies": { - "mssql1": { - "type": "mssql", - "connectionId": "DefaultConnection" - } - } +{ + "dependencies": { + "mssql1": { + "type": "mssql", + "connectionId": "DefaultConnection" + } + } } \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/Properties/serviceDependencies.local.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Properties/serviceDependencies.local.json similarity index 94% rename from AspNetCore.Reporting.Angular/Properties/serviceDependencies.local.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Properties/serviceDependencies.local.json index c01b353..48bec3b 100644 --- a/AspNetCore.Reporting.Angular/Properties/serviceDependencies.local.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Properties/serviceDependencies.local.json @@ -1,8 +1,8 @@ -{ - "dependencies": { - "mssql1": { - "type": "mssql.local", - "connectionId": "DefaultConnection" - } - } +{ + "dependencies": { + "mssql1": { + "type": "mssql.local", + "connectionId": "DefaultConnection" + } + } } \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/Reports/CourseListReport.Designer.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/CourseListReport.Designer.cs similarity index 98% rename from AspNetCore.Reporting.Angular/Reports/CourseListReport.Designer.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/CourseListReport.Designer.cs index 95df6cc..9fb5ba0 100644 --- a/AspNetCore.Reporting.Angular/Reports/CourseListReport.Designer.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/CourseListReport.Designer.cs @@ -1,142 +1,142 @@ -namespace AspNetCore.Reporting.Common.Reports { - partial class CourseListReport { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) { - if (disposing && (components != null)) { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - this.TopMargin = new DevExpress.XtraReports.UI.TopMarginBand(); - this.BottomMargin = new DevExpress.XtraReports.UI.BottomMarginBand(); - this.Detail = new DevExpress.XtraReports.UI.DetailBand(); - this.label1 = new DevExpress.XtraReports.UI.XRLabel(); - this.table1 = new DevExpress.XtraReports.UI.XRTable(); - this.tableRow1 = new DevExpress.XtraReports.UI.XRTableRow(); - this.tableCell1 = new DevExpress.XtraReports.UI.XRTableCell(); - this.tableCell2 = new DevExpress.XtraReports.UI.XRTableCell(); - this.objectDataSource1 = new DevExpress.DataAccess.ObjectBinding.ObjectDataSource(this.components); - ((System.ComponentModel.ISupportInitialize)(this.table1)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.objectDataSource1)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this)).BeginInit(); - // - // TopMargin - // - this.TopMargin.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.label1}); - this.TopMargin.HeightF = 97.91666F; - this.TopMargin.Name = "TopMargin"; - // - // BottomMargin - // - this.BottomMargin.Name = "BottomMargin"; - // - // Detail - // - this.Detail.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.table1}); - this.Detail.HeightF = 35.00001F; - this.Detail.Name = "Detail"; - // - // label1 - // - this.label1.Font = new DevExpress.Drawing.DXFont("Segoe UI Semibold", 20.25F, DevExpress.Drawing.DXFontStyle.Bold, DevExpress.Drawing.DXGraphicsUnit.Point, new DevExpress.Drawing.DXFontAdditionalProperty[] { - new DevExpress.Drawing.DXFontAdditionalProperty("GdiCharSet", ((byte)(0)))}); - this.label1.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 38.87501F); - this.label1.Multiline = true; - this.label1.Name = "label1"; - this.label1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label1.SizeF = new System.Drawing.SizeF(630F, 49.04166F); - this.label1.StylePriority.UseFont = false; - this.label1.Text = "Course List"; - // - // table1 - // - this.table1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 10.00001F); - this.table1.Name = "table1"; - this.table1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 96F); - this.table1.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { - this.tableRow1}); - this.table1.SizeF = new System.Drawing.SizeF(650F, 25F); - // - // tableRow1 - // - this.tableRow1.Cells.AddRange(new DevExpress.XtraReports.UI.XRTableCell[] { - this.tableCell1, - this.tableCell2}); - this.tableRow1.Name = "tableRow1"; - this.tableRow1.Weight = 11.5D; - // - // tableCell1 - // - this.tableCell1.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[CourseID]")}); - this.tableCell1.Multiline = true; - this.tableCell1.Name = "tableCell1"; - this.tableCell1.Text = "tableCell1"; - this.tableCell1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.TopRight; - this.tableCell1.Weight = 0.10185184837406516D; - // - // tableCell2 - // - this.tableCell2.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[CourseTitle]")}); - this.tableCell2.Multiline = true; - this.tableCell2.Name = "tableCell2"; - this.tableCell2.Text = "tableCell2"; - this.tableCell2.Weight = 0.34259259607037928D; - // - // objectDataSource1 - // - this.objectDataSource1.DataMember = "GetCourses"; - this.objectDataSource1.DataSource = typeof(global::AspNetCore.Reporting.Common.Services.CourseListReportRepository); - this.objectDataSource1.Name = "objectDataSource1"; - // - // CourseListReport - // - this.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] { - this.TopMargin, - this.BottomMargin, - this.Detail}); - this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] { - this.objectDataSource1}); - this.DataSource = this.objectDataSource1; - this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F); - this.Margins = new DevExpress.Drawing.DXMargins(100F, 100F, 97.91666F, 100F); - this.Version = "23.2"; - ((System.ComponentModel.ISupportInitialize)(this.table1)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.objectDataSource1)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this)).EndInit(); - - } - - #endregion - - private DevExpress.XtraReports.UI.TopMarginBand TopMargin; - private DevExpress.XtraReports.UI.XRLabel label1; - private DevExpress.XtraReports.UI.BottomMarginBand BottomMargin; - private DevExpress.XtraReports.UI.DetailBand Detail; - private DevExpress.XtraReports.UI.XRTable table1; - private DevExpress.XtraReports.UI.XRTableRow tableRow1; - private DevExpress.XtraReports.UI.XRTableCell tableCell1; - private DevExpress.XtraReports.UI.XRTableCell tableCell2; - private DevExpress.DataAccess.ObjectBinding.ObjectDataSource objectDataSource1; - } -} +namespace AspNetCore.Reporting.Common.Reports { + partial class CourseListReport { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); + this.TopMargin = new DevExpress.XtraReports.UI.TopMarginBand(); + this.BottomMargin = new DevExpress.XtraReports.UI.BottomMarginBand(); + this.Detail = new DevExpress.XtraReports.UI.DetailBand(); + this.label1 = new DevExpress.XtraReports.UI.XRLabel(); + this.table1 = new DevExpress.XtraReports.UI.XRTable(); + this.tableRow1 = new DevExpress.XtraReports.UI.XRTableRow(); + this.tableCell1 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell2 = new DevExpress.XtraReports.UI.XRTableCell(); + this.objectDataSource1 = new DevExpress.DataAccess.ObjectBinding.ObjectDataSource(this.components); + ((System.ComponentModel.ISupportInitialize)(this.table1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.objectDataSource1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this)).BeginInit(); + // + // TopMargin + // + this.TopMargin.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.label1}); + this.TopMargin.HeightF = 97.91666F; + this.TopMargin.Name = "TopMargin"; + // + // BottomMargin + // + this.BottomMargin.Name = "BottomMargin"; + // + // Detail + // + this.Detail.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.table1}); + this.Detail.HeightF = 35.00001F; + this.Detail.Name = "Detail"; + // + // label1 + // + this.label1.Font = new DevExpress.Drawing.DXFont("Segoe UI Semibold", 20.25F, DevExpress.Drawing.DXFontStyle.Bold, DevExpress.Drawing.DXGraphicsUnit.Point, new DevExpress.Drawing.DXFontAdditionalProperty[] { + new DevExpress.Drawing.DXFontAdditionalProperty("GdiCharSet", ((byte)(0)))}); + this.label1.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 38.87501F); + this.label1.Multiline = true; + this.label1.Name = "label1"; + this.label1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label1.SizeF = new System.Drawing.SizeF(630F, 49.04166F); + this.label1.StylePriority.UseFont = false; + this.label1.Text = "Course List"; + // + // table1 + // + this.table1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 10.00001F); + this.table1.Name = "table1"; + this.table1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 96F); + this.table1.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { + this.tableRow1}); + this.table1.SizeF = new System.Drawing.SizeF(650F, 25F); + // + // tableRow1 + // + this.tableRow1.Cells.AddRange(new DevExpress.XtraReports.UI.XRTableCell[] { + this.tableCell1, + this.tableCell2}); + this.tableRow1.Name = "tableRow1"; + this.tableRow1.Weight = 11.5D; + // + // tableCell1 + // + this.tableCell1.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[CourseID]")}); + this.tableCell1.Multiline = true; + this.tableCell1.Name = "tableCell1"; + this.tableCell1.Text = "tableCell1"; + this.tableCell1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.TopRight; + this.tableCell1.Weight = 0.10185184837406516D; + // + // tableCell2 + // + this.tableCell2.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[CourseTitle]")}); + this.tableCell2.Multiline = true; + this.tableCell2.Name = "tableCell2"; + this.tableCell2.Text = "tableCell2"; + this.tableCell2.Weight = 0.34259259607037928D; + // + // objectDataSource1 + // + this.objectDataSource1.DataMember = "GetCourses"; + this.objectDataSource1.DataSource = typeof(global::AspNetCore.Reporting.Common.Services.CourseListReportRepository); + this.objectDataSource1.Name = "objectDataSource1"; + // + // CourseListReport + // + this.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] { + this.TopMargin, + this.BottomMargin, + this.Detail}); + this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] { + this.objectDataSource1}); + this.DataSource = this.objectDataSource1; + this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F); + this.Margins = new DevExpress.Drawing.DXMargins(100F, 100F, 97.91666F, 100F); + this.Version = "23.2"; + ((System.ComponentModel.ISupportInitialize)(this.table1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.objectDataSource1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this)).EndInit(); + + } + + #endregion + + private DevExpress.XtraReports.UI.TopMarginBand TopMargin; + private DevExpress.XtraReports.UI.XRLabel label1; + private DevExpress.XtraReports.UI.BottomMarginBand BottomMargin; + private DevExpress.XtraReports.UI.DetailBand Detail; + private DevExpress.XtraReports.UI.XRTable table1; + private DevExpress.XtraReports.UI.XRTableRow tableRow1; + private DevExpress.XtraReports.UI.XRTableCell tableCell1; + private DevExpress.XtraReports.UI.XRTableCell tableCell2; + private DevExpress.DataAccess.ObjectBinding.ObjectDataSource objectDataSource1; + } +} diff --git a/AspNetCore.Reporting.Angular/Reports/CourseListReport.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/CourseListReport.cs similarity index 96% rename from AspNetCore.Reporting.Angular/Reports/CourseListReport.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/CourseListReport.cs index 0ff248f..6962b9f 100644 --- a/AspNetCore.Reporting.Angular/Reports/CourseListReport.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/CourseListReport.cs @@ -1,9 +1,9 @@ -using DevExpress.XtraReports.UI; - -namespace AspNetCore.Reporting.Common.Reports { - public partial class CourseListReport : XtraReport { - public CourseListReport() { - InitializeComponent(); - } - } -} +using DevExpress.XtraReports.UI; + +namespace AspNetCore.Reporting.Common.Reports { + public partial class CourseListReport : XtraReport { + public CourseListReport() { + InitializeComponent(); + } + } +} diff --git a/AspNetCore.Reporting.Angular/Reports/CourseListReport.resx b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/CourseListReport.resx similarity index 100% rename from AspNetCore.Reporting.Angular/Reports/CourseListReport.resx rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/CourseListReport.resx diff --git a/AspNetCore.Reporting.Angular/Reports/MyEnrollmentsReport.Designer.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/MyEnrollmentsReport.Designer.cs similarity index 98% rename from AspNetCore.Reporting.Angular/Reports/MyEnrollmentsReport.Designer.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/MyEnrollmentsReport.Designer.cs index 4efa298..cafcb04 100644 --- a/AspNetCore.Reporting.Angular/Reports/MyEnrollmentsReport.Designer.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/MyEnrollmentsReport.Designer.cs @@ -1,318 +1,318 @@ -namespace AspNetCore.Reporting.Common.Reports { - partial class MyEnrollmentsReport { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) { - if (disposing && (components != null)) { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - this.TopMargin = new DevExpress.XtraReports.UI.TopMarginBand(); - this.BottomMargin = new DevExpress.XtraReports.UI.BottomMarginBand(); - this.Detail = new DevExpress.XtraReports.UI.DetailBand(); - this.DetailReport = new DevExpress.XtraReports.UI.DetailReportBand(); - this.label1 = new DevExpress.XtraReports.UI.XRLabel(); - this.pageInfo1 = new DevExpress.XtraReports.UI.XRPageInfo(); - this.label9 = new DevExpress.XtraReports.UI.XRLabel(); - this.label8 = new DevExpress.XtraReports.UI.XRLabel(); - this.label7 = new DevExpress.XtraReports.UI.XRLabel(); - this.label6 = new DevExpress.XtraReports.UI.XRLabel(); - this.label5 = new DevExpress.XtraReports.UI.XRLabel(); - this.label4 = new DevExpress.XtraReports.UI.XRLabel(); - this.label3 = new DevExpress.XtraReports.UI.XRLabel(); - this.label2 = new DevExpress.XtraReports.UI.XRLabel(); - this.Detail1 = new DevExpress.XtraReports.UI.DetailBand(); - this.table1 = new DevExpress.XtraReports.UI.XRTable(); - this.tableRow1 = new DevExpress.XtraReports.UI.XRTableRow(); - this.tableCell1 = new DevExpress.XtraReports.UI.XRTableCell(); - this.tableCell2 = new DevExpress.XtraReports.UI.XRTableCell(); - this.tableCell3 = new DevExpress.XtraReports.UI.XRTableCell(); - this.odsEnrollments = new DevExpress.DataAccess.ObjectBinding.ObjectDataSource(this.components); - this.odsStudentDetails = new DevExpress.DataAccess.ObjectBinding.ObjectDataSource(this.components); - this.rpShowTimestamp = new DevExpress.XtraReports.Parameters.Parameter(); - ((System.ComponentModel.ISupportInitialize)(this.table1)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.odsEnrollments)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.odsStudentDetails)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this)).BeginInit(); - // - // TopMargin - // - this.TopMargin.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.label1}); - this.TopMargin.HeightF = 135.4167F; - this.TopMargin.Name = "TopMargin"; - // - // BottomMargin - // - this.BottomMargin.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.pageInfo1}); - this.BottomMargin.Name = "BottomMargin"; - // - // Detail - // - this.Detail.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.label9, - this.label8, - this.label7, - this.label6, - this.label5, - this.label4, - this.label3, - this.label2}); - this.Detail.Expanded = false; - this.Detail.HeightF = 114.5833F; - this.Detail.Name = "Detail"; - // - // DetailReport - // - this.DetailReport.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] { - this.Detail1}); - this.DetailReport.DataSource = this.odsEnrollments; - this.DetailReport.Level = 0; - this.DetailReport.Name = "DetailReport"; - // - // label1 - // - this.label1.Font = new DevExpress.Drawing.DXFont("Segoe UI Semibold", 20.25F, DevExpress.Drawing.DXFontStyle.Bold, DevExpress.Drawing.DXGraphicsUnit.Point, new DevExpress.Drawing.DXFontAdditionalProperty[] { - new DevExpress.Drawing.DXFontAdditionalProperty("GdiCharSet", ((byte)(0)))}); - this.label1.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 76.37501F); - this.label1.Multiline = true; - this.label1.Name = "label1"; - this.label1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label1.SizeF = new System.Drawing.SizeF(630F, 49.04166F); - this.label1.StylePriority.UseFont = false; - this.label1.Text = "Enrollments Report"; - // - // pageInfo1 - // - this.pageInfo1.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Visible", "?rpShowTimestamp")}); - this.pageInfo1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 10.00001F); - this.pageInfo1.Name = "pageInfo1"; - this.pageInfo1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.pageInfo1.PageInfo = DevExpress.XtraPrinting.PageInfo.DateTime; - this.pageInfo1.SizeF = new System.Drawing.SizeF(650F, 23F); - // - // label9 - // - this.label9.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[EnrollmentDate]")}); - this.label9.LocationFloat = new DevExpress.Utils.PointFloat(261.4583F, 79.00003F); - this.label9.Multiline = true; - this.label9.Name = "label9"; - this.label9.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label9.SizeF = new System.Drawing.SizeF(336.4583F, 23F); - this.label9.Text = "label9"; - this.label9.TextFormatString = "{0:d}"; - // - // label8 - // - this.label8.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 79.00003F); - this.label8.Multiline = true; - this.label8.Name = "label8"; - this.label8.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label8.SizeF = new System.Drawing.SizeF(238.5416F, 23F); - this.label8.Text = "Enrollment Date:"; - // - // label7 - // - this.label7.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[LastName]")}); - this.label7.LocationFloat = new DevExpress.Utils.PointFloat(261.4583F, 56.00001F); - this.label7.Multiline = true; - this.label7.Name = "label7"; - this.label7.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label7.SizeF = new System.Drawing.SizeF(336.4583F, 23F); - this.label7.Text = "label7"; - // - // label6 - // - this.label6.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 56.00001F); - this.label6.Multiline = true; - this.label6.Name = "label6"; - this.label6.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label6.SizeF = new System.Drawing.SizeF(238.5416F, 23F); - this.label6.Text = "Last Name:"; - // - // label5 - // - this.label5.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[FirstMidName]")}); - this.label5.LocationFloat = new DevExpress.Utils.PointFloat(261.4583F, 32.99999F); - this.label5.Multiline = true; - this.label5.Name = "label5"; - this.label5.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label5.SizeF = new System.Drawing.SizeF(336.4584F, 23F); - this.label5.Text = "label5"; - // - // label4 - // - this.label4.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 32.99999F); - this.label4.Multiline = true; - this.label4.Name = "label4"; - this.label4.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label4.SizeF = new System.Drawing.SizeF(238.5416F, 23F); - this.label4.Text = "Name:"; - // - // label3 - // - this.label3.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[StudentID]")}); - this.label3.LocationFloat = new DevExpress.Utils.PointFloat(261.4583F, 10.00001F); - this.label3.Multiline = true; - this.label3.Name = "label3"; - this.label3.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label3.SizeF = new System.Drawing.SizeF(336.4583F, 23F); - this.label3.StylePriority.UseTextAlignment = false; - this.label3.Text = "label3"; - this.label3.TextAlignment = DevExpress.XtraPrinting.TextAlignment.TopLeft; - // - // label2 - // - this.label2.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 10.00001F); - this.label2.Multiline = true; - this.label2.Name = "label2"; - this.label2.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); - this.label2.SizeF = new System.Drawing.SizeF(238.5416F, 23F); - this.label2.Text = "ID:"; - // - // Detail1 - // - this.Detail1.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.table1}); - this.Detail1.HeightF = 35.00001F; - this.Detail1.Name = "Detail1"; - // - // table1 - // - this.table1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 10.00001F); - this.table1.Name = "table1"; - this.table1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 96F); - this.table1.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { - this.tableRow1}); - this.table1.SizeF = new System.Drawing.SizeF(650F, 25F); - // - // tableRow1 - // - this.tableRow1.Cells.AddRange(new DevExpress.XtraReports.UI.XRTableCell[] { - this.tableCell1, - this.tableCell2, - this.tableCell3}); - this.tableRow1.Name = "tableRow1"; - this.tableRow1.Weight = 11.5D; - // - // tableCell1 - // - this.tableCell1.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[EnrollmentID]")}); - this.tableCell1.Multiline = true; - this.tableCell1.Name = "tableCell1"; - this.tableCell1.Text = "tableCell1"; - this.tableCell1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.TopRight; - this.tableCell1.Weight = 0.13324174818101819D; - // - // tableCell2 - // - this.tableCell2.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[CourseTitle]")}); - this.tableCell2.Multiline = true; - this.tableCell2.Name = "tableCell2"; - this.tableCell2.Text = "tableCell2"; - this.tableCell2.Weight = 0.4381868232475532D; - // - // tableCell3 - // - this.tableCell3.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[Grade]")}); - this.tableCell3.Multiline = true; - this.tableCell3.Name = "tableCell3"; - this.tableCell3.Text = "tableCell3"; - this.tableCell3.Weight = 0.2857142857142857D; - // - // odsEnrollments - // - this.odsEnrollments.DataMember = "GetEnrollments"; - this.odsEnrollments.DataSource = typeof(global::AspNetCore.Reporting.Common.Services.MyEnrollmentsReportRepository); - this.odsEnrollments.Name = "odsEnrollments"; - // - // odsStudentDetails - // - this.odsStudentDetails.DataMember = "GetStudentDetails"; - this.odsStudentDetails.DataSource = typeof(global::AspNetCore.Reporting.Common.Services.MyEnrollmentsReportRepository); - this.odsStudentDetails.Name = "odsStudentDetails"; - // - // rpShowTimestamp - // - this.rpShowTimestamp.Description = "Show Timestamp"; - this.rpShowTimestamp.Name = "rpShowTimestamp"; - this.rpShowTimestamp.Type = typeof(bool); - this.rpShowTimestamp.ValueInfo = "False"; - // - // MyEnrollmentsReport - // - this.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] { - this.TopMargin, - this.BottomMargin, - this.Detail, - this.DetailReport}); - this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] { - this.odsStudentDetails, - this.odsEnrollments}); - this.DataSource = this.odsStudentDetails; - this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F); - this.Margins = new DevExpress.Drawing.DXMargins(100F, 100F, 135.4167F, 100F); - this.Parameters.AddRange(new DevExpress.XtraReports.Parameters.Parameter[] { - this.rpShowTimestamp}); - this.RequestParameters = false; - this.Version = "23.2"; - ((System.ComponentModel.ISupportInitialize)(this.table1)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.odsEnrollments)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.odsStudentDetails)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this)).EndInit(); - - } - - #endregion - - private DevExpress.XtraReports.UI.TopMarginBand TopMargin; - private DevExpress.XtraReports.UI.XRLabel label1; - private DevExpress.XtraReports.UI.BottomMarginBand BottomMargin; - private DevExpress.XtraReports.UI.XRPageInfo pageInfo1; - private DevExpress.XtraReports.UI.DetailBand Detail; - private DevExpress.XtraReports.UI.XRLabel label9; - private DevExpress.XtraReports.UI.XRLabel label8; - private DevExpress.XtraReports.UI.XRLabel label7; - private DevExpress.XtraReports.UI.XRLabel label6; - private DevExpress.XtraReports.UI.XRLabel label5; - private DevExpress.XtraReports.UI.XRLabel label4; - private DevExpress.XtraReports.UI.XRLabel label3; - private DevExpress.XtraReports.UI.XRLabel label2; - private DevExpress.XtraReports.UI.DetailReportBand DetailReport; - private DevExpress.XtraReports.UI.DetailBand Detail1; - private DevExpress.XtraReports.UI.XRTable table1; - private DevExpress.XtraReports.UI.XRTableRow tableRow1; - private DevExpress.XtraReports.UI.XRTableCell tableCell1; - private DevExpress.XtraReports.UI.XRTableCell tableCell2; - private DevExpress.XtraReports.UI.XRTableCell tableCell3; - private DevExpress.DataAccess.ObjectBinding.ObjectDataSource odsEnrollments; - private DevExpress.DataAccess.ObjectBinding.ObjectDataSource odsStudentDetails; - private DevExpress.XtraReports.Parameters.Parameter rpShowTimestamp; - } -} +namespace AspNetCore.Reporting.Common.Reports { + partial class MyEnrollmentsReport { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); + this.TopMargin = new DevExpress.XtraReports.UI.TopMarginBand(); + this.BottomMargin = new DevExpress.XtraReports.UI.BottomMarginBand(); + this.Detail = new DevExpress.XtraReports.UI.DetailBand(); + this.DetailReport = new DevExpress.XtraReports.UI.DetailReportBand(); + this.label1 = new DevExpress.XtraReports.UI.XRLabel(); + this.pageInfo1 = new DevExpress.XtraReports.UI.XRPageInfo(); + this.label9 = new DevExpress.XtraReports.UI.XRLabel(); + this.label8 = new DevExpress.XtraReports.UI.XRLabel(); + this.label7 = new DevExpress.XtraReports.UI.XRLabel(); + this.label6 = new DevExpress.XtraReports.UI.XRLabel(); + this.label5 = new DevExpress.XtraReports.UI.XRLabel(); + this.label4 = new DevExpress.XtraReports.UI.XRLabel(); + this.label3 = new DevExpress.XtraReports.UI.XRLabel(); + this.label2 = new DevExpress.XtraReports.UI.XRLabel(); + this.Detail1 = new DevExpress.XtraReports.UI.DetailBand(); + this.table1 = new DevExpress.XtraReports.UI.XRTable(); + this.tableRow1 = new DevExpress.XtraReports.UI.XRTableRow(); + this.tableCell1 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell2 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell3 = new DevExpress.XtraReports.UI.XRTableCell(); + this.odsEnrollments = new DevExpress.DataAccess.ObjectBinding.ObjectDataSource(this.components); + this.odsStudentDetails = new DevExpress.DataAccess.ObjectBinding.ObjectDataSource(this.components); + this.rpShowTimestamp = new DevExpress.XtraReports.Parameters.Parameter(); + ((System.ComponentModel.ISupportInitialize)(this.table1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.odsEnrollments)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.odsStudentDetails)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this)).BeginInit(); + // + // TopMargin + // + this.TopMargin.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.label1}); + this.TopMargin.HeightF = 135.4167F; + this.TopMargin.Name = "TopMargin"; + // + // BottomMargin + // + this.BottomMargin.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.pageInfo1}); + this.BottomMargin.Name = "BottomMargin"; + // + // Detail + // + this.Detail.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.label9, + this.label8, + this.label7, + this.label6, + this.label5, + this.label4, + this.label3, + this.label2}); + this.Detail.Expanded = false; + this.Detail.HeightF = 114.5833F; + this.Detail.Name = "Detail"; + // + // DetailReport + // + this.DetailReport.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] { + this.Detail1}); + this.DetailReport.DataSource = this.odsEnrollments; + this.DetailReport.Level = 0; + this.DetailReport.Name = "DetailReport"; + // + // label1 + // + this.label1.Font = new DevExpress.Drawing.DXFont("Segoe UI Semibold", 20.25F, DevExpress.Drawing.DXFontStyle.Bold, DevExpress.Drawing.DXGraphicsUnit.Point, new DevExpress.Drawing.DXFontAdditionalProperty[] { + new DevExpress.Drawing.DXFontAdditionalProperty("GdiCharSet", ((byte)(0)))}); + this.label1.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 76.37501F); + this.label1.Multiline = true; + this.label1.Name = "label1"; + this.label1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label1.SizeF = new System.Drawing.SizeF(630F, 49.04166F); + this.label1.StylePriority.UseFont = false; + this.label1.Text = "Enrollments Report"; + // + // pageInfo1 + // + this.pageInfo1.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Visible", "?rpShowTimestamp")}); + this.pageInfo1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 10.00001F); + this.pageInfo1.Name = "pageInfo1"; + this.pageInfo1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.pageInfo1.PageInfo = DevExpress.XtraPrinting.PageInfo.DateTime; + this.pageInfo1.SizeF = new System.Drawing.SizeF(650F, 23F); + // + // label9 + // + this.label9.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[EnrollmentDate]")}); + this.label9.LocationFloat = new DevExpress.Utils.PointFloat(261.4583F, 79.00003F); + this.label9.Multiline = true; + this.label9.Name = "label9"; + this.label9.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label9.SizeF = new System.Drawing.SizeF(336.4583F, 23F); + this.label9.Text = "label9"; + this.label9.TextFormatString = "{0:d}"; + // + // label8 + // + this.label8.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 79.00003F); + this.label8.Multiline = true; + this.label8.Name = "label8"; + this.label8.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label8.SizeF = new System.Drawing.SizeF(238.5416F, 23F); + this.label8.Text = "Enrollment Date:"; + // + // label7 + // + this.label7.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[LastName]")}); + this.label7.LocationFloat = new DevExpress.Utils.PointFloat(261.4583F, 56.00001F); + this.label7.Multiline = true; + this.label7.Name = "label7"; + this.label7.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label7.SizeF = new System.Drawing.SizeF(336.4583F, 23F); + this.label7.Text = "label7"; + // + // label6 + // + this.label6.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 56.00001F); + this.label6.Multiline = true; + this.label6.Name = "label6"; + this.label6.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label6.SizeF = new System.Drawing.SizeF(238.5416F, 23F); + this.label6.Text = "Last Name:"; + // + // label5 + // + this.label5.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[FirstMidName]")}); + this.label5.LocationFloat = new DevExpress.Utils.PointFloat(261.4583F, 32.99999F); + this.label5.Multiline = true; + this.label5.Name = "label5"; + this.label5.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label5.SizeF = new System.Drawing.SizeF(336.4584F, 23F); + this.label5.Text = "label5"; + // + // label4 + // + this.label4.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 32.99999F); + this.label4.Multiline = true; + this.label4.Name = "label4"; + this.label4.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label4.SizeF = new System.Drawing.SizeF(238.5416F, 23F); + this.label4.Text = "Name:"; + // + // label3 + // + this.label3.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[StudentID]")}); + this.label3.LocationFloat = new DevExpress.Utils.PointFloat(261.4583F, 10.00001F); + this.label3.Multiline = true; + this.label3.Name = "label3"; + this.label3.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label3.SizeF = new System.Drawing.SizeF(336.4583F, 23F); + this.label3.StylePriority.UseTextAlignment = false; + this.label3.Text = "label3"; + this.label3.TextAlignment = DevExpress.XtraPrinting.TextAlignment.TopLeft; + // + // label2 + // + this.label2.LocationFloat = new DevExpress.Utils.PointFloat(10.00001F, 10.00001F); + this.label2.Multiline = true; + this.label2.Name = "label2"; + this.label2.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 100F); + this.label2.SizeF = new System.Drawing.SizeF(238.5416F, 23F); + this.label2.Text = "ID:"; + // + // Detail1 + // + this.Detail1.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.table1}); + this.Detail1.HeightF = 35.00001F; + this.Detail1.Name = "Detail1"; + // + // table1 + // + this.table1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 10.00001F); + this.table1.Name = "table1"; + this.table1.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 0, 0, 96F); + this.table1.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { + this.tableRow1}); + this.table1.SizeF = new System.Drawing.SizeF(650F, 25F); + // + // tableRow1 + // + this.tableRow1.Cells.AddRange(new DevExpress.XtraReports.UI.XRTableCell[] { + this.tableCell1, + this.tableCell2, + this.tableCell3}); + this.tableRow1.Name = "tableRow1"; + this.tableRow1.Weight = 11.5D; + // + // tableCell1 + // + this.tableCell1.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[EnrollmentID]")}); + this.tableCell1.Multiline = true; + this.tableCell1.Name = "tableCell1"; + this.tableCell1.Text = "tableCell1"; + this.tableCell1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.TopRight; + this.tableCell1.Weight = 0.13324174818101819D; + // + // tableCell2 + // + this.tableCell2.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[CourseTitle]")}); + this.tableCell2.Multiline = true; + this.tableCell2.Name = "tableCell2"; + this.tableCell2.Text = "tableCell2"; + this.tableCell2.Weight = 0.4381868232475532D; + // + // tableCell3 + // + this.tableCell3.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[Grade]")}); + this.tableCell3.Multiline = true; + this.tableCell3.Name = "tableCell3"; + this.tableCell3.Text = "tableCell3"; + this.tableCell3.Weight = 0.2857142857142857D; + // + // odsEnrollments + // + this.odsEnrollments.DataMember = "GetEnrollments"; + this.odsEnrollments.DataSource = typeof(global::AspNetCore.Reporting.Common.Services.MyEnrollmentsReportRepository); + this.odsEnrollments.Name = "odsEnrollments"; + // + // odsStudentDetails + // + this.odsStudentDetails.DataMember = "GetStudentDetails"; + this.odsStudentDetails.DataSource = typeof(global::AspNetCore.Reporting.Common.Services.MyEnrollmentsReportRepository); + this.odsStudentDetails.Name = "odsStudentDetails"; + // + // rpShowTimestamp + // + this.rpShowTimestamp.Description = "Show Timestamp"; + this.rpShowTimestamp.Name = "rpShowTimestamp"; + this.rpShowTimestamp.Type = typeof(bool); + this.rpShowTimestamp.ValueInfo = "False"; + // + // MyEnrollmentsReport + // + this.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] { + this.TopMargin, + this.BottomMargin, + this.Detail, + this.DetailReport}); + this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] { + this.odsStudentDetails, + this.odsEnrollments}); + this.DataSource = this.odsStudentDetails; + this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F); + this.Margins = new DevExpress.Drawing.DXMargins(100F, 100F, 135.4167F, 100F); + this.Parameters.AddRange(new DevExpress.XtraReports.Parameters.Parameter[] { + this.rpShowTimestamp}); + this.RequestParameters = false; + this.Version = "23.2"; + ((System.ComponentModel.ISupportInitialize)(this.table1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.odsEnrollments)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.odsStudentDetails)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this)).EndInit(); + + } + + #endregion + + private DevExpress.XtraReports.UI.TopMarginBand TopMargin; + private DevExpress.XtraReports.UI.XRLabel label1; + private DevExpress.XtraReports.UI.BottomMarginBand BottomMargin; + private DevExpress.XtraReports.UI.XRPageInfo pageInfo1; + private DevExpress.XtraReports.UI.DetailBand Detail; + private DevExpress.XtraReports.UI.XRLabel label9; + private DevExpress.XtraReports.UI.XRLabel label8; + private DevExpress.XtraReports.UI.XRLabel label7; + private DevExpress.XtraReports.UI.XRLabel label6; + private DevExpress.XtraReports.UI.XRLabel label5; + private DevExpress.XtraReports.UI.XRLabel label4; + private DevExpress.XtraReports.UI.XRLabel label3; + private DevExpress.XtraReports.UI.XRLabel label2; + private DevExpress.XtraReports.UI.DetailReportBand DetailReport; + private DevExpress.XtraReports.UI.DetailBand Detail1; + private DevExpress.XtraReports.UI.XRTable table1; + private DevExpress.XtraReports.UI.XRTableRow tableRow1; + private DevExpress.XtraReports.UI.XRTableCell tableCell1; + private DevExpress.XtraReports.UI.XRTableCell tableCell2; + private DevExpress.XtraReports.UI.XRTableCell tableCell3; + private DevExpress.DataAccess.ObjectBinding.ObjectDataSource odsEnrollments; + private DevExpress.DataAccess.ObjectBinding.ObjectDataSource odsStudentDetails; + private DevExpress.XtraReports.Parameters.Parameter rpShowTimestamp; + } +} diff --git a/AspNetCore.Reporting.Angular/Reports/MyEnrollmentsReport.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/MyEnrollmentsReport.cs similarity index 96% rename from AspNetCore.Reporting.Angular/Reports/MyEnrollmentsReport.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/MyEnrollmentsReport.cs index d2fdd2e..6adb9f8 100644 --- a/AspNetCore.Reporting.Angular/Reports/MyEnrollmentsReport.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/MyEnrollmentsReport.cs @@ -1,9 +1,9 @@ -using DevExpress.XtraReports.UI; - -namespace AspNetCore.Reporting.Common.Reports { - public partial class MyEnrollmentsReport : XtraReport { - public MyEnrollmentsReport() { - InitializeComponent(); - } - } -} +using DevExpress.XtraReports.UI; + +namespace AspNetCore.Reporting.Common.Reports { + public partial class MyEnrollmentsReport : XtraReport { + public MyEnrollmentsReport() { + InitializeComponent(); + } + } +} diff --git a/AspNetCore.Reporting.Angular/Reports/MyEnrollmentsReport.resx b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/MyEnrollmentsReport.resx similarity index 100% rename from AspNetCore.Reporting.Angular/Reports/MyEnrollmentsReport.resx rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/MyEnrollmentsReport.resx diff --git a/AspNetCore.Reporting.Angular/Reports/ReportsFactory.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/ReportsFactory.cs similarity index 87% rename from AspNetCore.Reporting.Angular/Reports/ReportsFactory.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/ReportsFactory.cs index 4066967..80e6ed7 100644 --- a/AspNetCore.Reporting.Angular/Reports/ReportsFactory.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Reports/ReportsFactory.cs @@ -1,16 +1,14 @@ -using System; -using System.Collections.Generic; -using DevExpress.XtraReports.UI; - -namespace AspNetCore.Reporting.Common.Reports { - public class ReportsFactory { - public Dictionary> Reports { - get { - return new Dictionary>() { - ["Enrollments"] = () => new MyEnrollmentsReport(), - ["CourseList"] = () => new CourseListReport(), - }; - } - } - } -} +using DevExpress.XtraReports.UI; + +namespace AspNetCore.Reporting.Common.Reports { + public class ReportsFactory { + public Dictionary> Reports { + get { + return new Dictionary>() { + ["Enrollments"] = () => new MyEnrollmentsReport(), + ["CourseList"] = () => new CourseListReport(), + }; + } + } + } +} diff --git a/AspNetCore.Reporting.Angular/Services/CourseListReportRepository.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/CourseListReportRepository.cs similarity index 92% rename from AspNetCore.Reporting.Angular/Services/CourseListReportRepository.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/CourseListReportRepository.cs index 4d91e3f..38057fa 100644 --- a/AspNetCore.Reporting.Angular/Services/CourseListReportRepository.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/CourseListReportRepository.cs @@ -1,33 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AspNetCore.Reporting.Angular.Data; -using AspNetCore.Reporting.Common.Models; - -namespace AspNetCore.Reporting.Common.Services { - public class CourseListReportRepository { - readonly IScopedDbContextProvider scopedDbContextProvider; - - public CourseListReportRepository() { - // We use this parameterless constructor in the Data Source Wizard only, and not for the actual instantiation of the repository object. - throw new NotSupportedException(); - } - - public CourseListReportRepository(IScopedDbContextProvider scopedDbContextProvider) { - this.scopedDbContextProvider = scopedDbContextProvider ?? throw new ArgumentNullException(nameof(scopedDbContextProvider)); - } - - public IList GetCourses() { - using(var dbContextScope = scopedDbContextProvider.GetDbContextScope()) { - var dbContext = dbContextScope.DbContext; - var model = dbContext.Courses.Select(x => - new CourseModel { - CourseID = x.CourseID, - CourseTitle = x.Title - }) - .ToList(); - return model; - } - } - } -} +using AspNetCore.Reporting.Angular.Data; +using AspNetCore.Reporting.Common.Models; + +namespace AspNetCore.Reporting.Common.Services { + public class CourseListReportRepository { + readonly IScopedDbContextProvider scopedDbContextProvider; + + public CourseListReportRepository() { + // We use this parameterless constructor in the Data Source Wizard only, and not for the actual instantiation of the repository object. + throw new NotSupportedException(); + } + + public CourseListReportRepository(IScopedDbContextProvider scopedDbContextProvider) { + this.scopedDbContextProvider = scopedDbContextProvider ?? throw new ArgumentNullException(nameof(scopedDbContextProvider)); + } + + public IList GetCourses() { + using(var dbContextScope = scopedDbContextProvider.GetDbContextScope()) { + var dbContext = dbContextScope.DbContext; + var model = dbContext.Courses.Select(x => + new CourseModel { + CourseID = x.CourseID, + CourseTitle = x.Title + }) + .ToList(); + return model; + } + } + } +} diff --git a/AspNetCore.Reporting.Angular/Services/CustomObjectDataSourceWizardTypeProvider.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/CustomObjectDataSourceWizardTypeProvider.cs similarity index 87% rename from AspNetCore.Reporting.Angular/Services/CustomObjectDataSourceWizardTypeProvider.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/CustomObjectDataSourceWizardTypeProvider.cs index ab4ae03..041afb4 100644 --- a/AspNetCore.Reporting.Angular/Services/CustomObjectDataSourceWizardTypeProvider.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/CustomObjectDataSourceWizardTypeProvider.cs @@ -1,14 +1,12 @@ -using System; -using System.Collections.Generic; -using DevExpress.DataAccess.Web; - -namespace AspNetCore.Reporting.Common.Services { - public class CustomObjectDataSourceWizardTypeProvider : IObjectDataSourceWizardTypeProvider { - public IEnumerable GetAvailableTypes(string context) { - return new[] { - typeof(CourseListReportRepository), - typeof(MyEnrollmentsReportRepository) - }; - } - } -} +using DevExpress.DataAccess.Web; + +namespace AspNetCore.Reporting.Common.Services { + public class CustomObjectDataSourceWizardTypeProvider : IObjectDataSourceWizardTypeProvider { + public IEnumerable GetAvailableTypes(string context) { + return new[] { + typeof(CourseListReportRepository), + typeof(MyEnrollmentsReportRepository) + }; + } + } +} diff --git a/AspNetCore.Reporting.Angular/Services/MyEnrollmentsReportRepository.cs b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/MyEnrollmentsReportRepository.cs similarity index 95% rename from AspNetCore.Reporting.Angular/Services/MyEnrollmentsReportRepository.cs rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/MyEnrollmentsReportRepository.cs index 19d4f2c..3273b96 100644 --- a/AspNetCore.Reporting.Angular/Services/MyEnrollmentsReportRepository.cs +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/Services/MyEnrollmentsReportRepository.cs @@ -1,59 +1,56 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AspNetCore.Reporting.Angular.Data; -using AspNetCore.Reporting.Common.Models; - -namespace AspNetCore.Reporting.Common.Services { - public class MyEnrollmentsReportRepository { - readonly string studentId; - readonly IScopedDbContextProvider scopedDbContextProvider; - - public MyEnrollmentsReportRepository() { - // We use this parameterless constructor in the Data Source Wizard only, and not for the actual instantiation of the repository object. - throw new NotSupportedException(); - } - - public MyEnrollmentsReportRepository(IScopedDbContextProvider scopedDbContextProvider, IAuthenticatiedUserService userService) { - this.scopedDbContextProvider = scopedDbContextProvider ?? throw new ArgumentNullException(nameof(scopedDbContextProvider)); - - // NOTE: the repository ctor is invoked in the context of http request. At this point of execution we have access to context-dependent data, like currentUserId. - // The repository MUST read and store all the required context-dependent values for later use. E.g. notice that we do not store the IUserService (which is context/scope dependent), but read the value of current user and store it. - studentId = userService.GetCurrentUserId(); - } - - public StudentDetailsModel GetStudentDetails() { - using(var dbContextScope = scopedDbContextProvider.GetDbContextScope()) { - var dbContext = dbContextScope.DbContext; - var student = dbContext.Students.Find(studentId); - - var model = new StudentDetailsModel { - StudentID = student.Id, - FirstMidName = student.FirstMidName, - LastName = student.LastName, - EnrollmentDate = student.EnrollmentDate - }; - return model; - } - } - - public IList GetEnrollments() { - using(var dbContextScope = scopedDbContextProvider.GetDbContextScope()) { - var dbContext = dbContextScope.DbContext; - var student = dbContext.Students.Find(studentId); - - dbContext.Entry(student).Collection(x => x.Enrollments).Load(); - student.Enrollments.ToList().ForEach(x => dbContext.Entry(x).Reference(c => c.Course).Load()); - - var enrollmentModels = student.Enrollments.Select(x => - new EnrollmentDetailsModel { - EnrollmentID = x.EnrollmentID, - CourseTitle = x.Course.Title, - Grade = x.Grade.HasValue ? x.Grade.Value.ToString() : "NO GRADE YET" - }); - - return enrollmentModels.ToList(); - } - } - } -} +using AspNetCore.Reporting.Angular.Data; +using AspNetCore.Reporting.Common.Models; + +namespace AspNetCore.Reporting.Common.Services { + public class MyEnrollmentsReportRepository { + readonly string studentId; + readonly IScopedDbContextProvider scopedDbContextProvider; + + public MyEnrollmentsReportRepository() { + // We use this parameterless constructor in the Data Source Wizard only, and not for the actual instantiation of the repository object. + throw new NotSupportedException(); + } + + public MyEnrollmentsReportRepository(IScopedDbContextProvider scopedDbContextProvider, IAuthenticatiedUserService userService) { + this.scopedDbContextProvider = scopedDbContextProvider ?? throw new ArgumentNullException(nameof(scopedDbContextProvider)); + + // NOTE: the repository ctor is invoked in the context of http request. At this point of execution we have access to context-dependent data, like currentUserId. + // The repository MUST read and store all the required context-dependent values for later use. E.g. notice that we do not store the IUserService (which is context/scope dependent), but read the value of current user and store it. + studentId = userService.GetCurrentUserId(); + } + + public StudentDetailsModel GetStudentDetails() { + using(var dbContextScope = scopedDbContextProvider.GetDbContextScope()) { + var dbContext = dbContextScope.DbContext; + var student = dbContext.Students.Find(studentId); + + var model = new StudentDetailsModel { + StudentID = student.Id, + FirstMidName = student.FirstMidName, + LastName = student.LastName, + EnrollmentDate = student.EnrollmentDate + }; + return model; + } + } + + public IList GetEnrollments() { + using(var dbContextScope = scopedDbContextProvider.GetDbContextScope()) { + var dbContext = dbContextScope.DbContext; + var student = dbContext.Students.Find(studentId); + + dbContext.Entry(student).Collection(x => x.Enrollments).Load(); + student.Enrollments.ToList().ForEach(x => dbContext.Entry(x).Reference(c => c.Course).Load()); + + var enrollmentModels = student.Enrollments.Select(x => + new EnrollmentDetailsModel { + EnrollmentID = x.EnrollmentID, + CourseTitle = x.Course.Title, + Grade = x.Grade.HasValue ? x.Grade.Value.ToString() : "NO GRADE YET" + }); + + return enrollmentModels.ToList(); + } + } + } +} diff --git a/AspNetCore.Reporting.Angular/appsettings.Development.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/appsettings.Development.json similarity index 93% rename from AspNetCore.Reporting.Angular/appsettings.Development.json rename to AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/appsettings.Development.json index e0cb0a0..ac35fa2 100644 --- a/AspNetCore.Reporting.Angular/appsettings.Development.json +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/appsettings.Development.json @@ -1,13 +1,13 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "DevExpress": "Debug" - } - }, - "IdentityServer": { - "Key": { - "Type": "Development" - } - } -} +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "DevExpress": "Debug" + } + }, + "IdentityServer": { + "Key": { + "Type": "Development" + } + } +} diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/appsettings.json b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/appsettings.json new file mode 100644 index 0000000..30860e1 --- /dev/null +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.Server/appsettings.json @@ -0,0 +1,43 @@ +{ + "ConnectionStrings": { + "DefaultMSSqlConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-AspNetCore.Reporting.Angular-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true", + "DefaultSqliteConnection": "Data Source=Database/application.db" + }, + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "IdentityServer": { + "Authority": "https://localhost:5001", + "Scope": "AspNetCore.Reporting.Angular.ServerAPI", + "Clients": { + "AspNetCore.Reporting.Angular.Client": { + "Profile": "SPA", + "ClientId": "AspNetCore.Reporting.Angular.Client", + "RequirePkce": true, + "RequireClientSecret": false, + "RedirectUri": "https://localhost:4200/authentication/login-callback", + "LogoutUri": "https://localhost:4200/authentication/logout-callback", + "RedirectUris": [ + "https://localhost:4200/authentication/login-callback" + ], + "PostLogoutRedirectUris": [ + "https://localhost:4200/authentication/logout-callback" + ], + "AllowedCorsOrigins": [ + "https://localhost:4200" + ], + "AllowedScopes": [ + "openid", + "profile", + "email", + "AspNetCore.Reporting.Angular.ServerAPI" + ] + } + } + }, + "AllowedHosts": "*" +} diff --git a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.sln b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.sln index 627f397..1412bcb 100644 --- a/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.sln +++ b/AspNetCore.Reporting.Angular/AspNetCore.Reporting.Angular.sln @@ -1,11 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35506.116 d17.12 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.Reporting.Angular", "AspNetCore.Reporting.Angular.csproj", "{E14C26B4-A5A1-4E27-9487-4132F4060DBB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.Reporting.Angular", "AspNetCore.Reporting.Angular.Server\AspNetCore.Reporting.Angular.Server.csproj", "{E14C26B4-A5A1-4E27-9487-4132F4060DBB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.Reporting.Common", "..\AspNetCore.Reporting.Common\AspNetCore.Reporting.Common.csproj", "{3DF36EF4-69E2-4692-BFE0-94E5994BAA96}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.Reporting.Common", "..\AspNetCore.Reporting.Common\AspNetCore.Reporting.Common.csproj", "{C1D03584-7F0B-4C90-9247-01A919B1413B}" +EndProject +Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "AspNetCore.Reporting.Angular.Client", "AspNetCore.Reporting.Angular.Client\AspNetCore.Reporting.Angular.Client.esproj", "{DA7E4198-BAA3-45B8-A3F9-01D2CFC000B4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,10 +19,16 @@ Global {E14C26B4-A5A1-4E27-9487-4132F4060DBB}.Debug|Any CPU.Build.0 = Debug|Any CPU {E14C26B4-A5A1-4E27-9487-4132F4060DBB}.Release|Any CPU.ActiveCfg = Release|Any CPU {E14C26B4-A5A1-4E27-9487-4132F4060DBB}.Release|Any CPU.Build.0 = Release|Any CPU - {3DF36EF4-69E2-4692-BFE0-94E5994BAA96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3DF36EF4-69E2-4692-BFE0-94E5994BAA96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3DF36EF4-69E2-4692-BFE0-94E5994BAA96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3DF36EF4-69E2-4692-BFE0-94E5994BAA96}.Release|Any CPU.Build.0 = Release|Any CPU + {C1D03584-7F0B-4C90-9247-01A919B1413B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1D03584-7F0B-4C90-9247-01A919B1413B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1D03584-7F0B-4C90-9247-01A919B1413B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1D03584-7F0B-4C90-9247-01A919B1413B}.Release|Any CPU.Build.0 = Release|Any CPU + {DA7E4198-BAA3-45B8-A3F9-01D2CFC000B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA7E4198-BAA3-45B8-A3F9-01D2CFC000B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA7E4198-BAA3-45B8-A3F9-01D2CFC000B4}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {DA7E4198-BAA3-45B8-A3F9-01D2CFC000B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA7E4198-BAA3-45B8-A3F9-01D2CFC000B4}.Release|Any CPU.Build.0 = Release|Any CPU + {DA7E4198-BAA3-45B8-A3F9-01D2CFC000B4}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AspNetCore.Reporting.Angular/ClientApp/src/environments/environment.prod.ts b/AspNetCore.Reporting.Angular/ClientApp/src/environments/environment.prod.ts deleted file mode 100644 index 0783a04..0000000 --- a/AspNetCore.Reporting.Angular/ClientApp/src/environments/environment.prod.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: true -}; diff --git a/AspNetCore.Reporting.Angular/ClientApp/tsconfig.app.json b/AspNetCore.Reporting.Angular/ClientApp/tsconfig.app.json deleted file mode 100644 index 5d9bdb9..0000000 --- a/AspNetCore.Reporting.Angular/ClientApp/tsconfig.app.json +++ /dev/null @@ -1,13 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [] -} diff --git a/AspNetCore.Reporting.Angular/Data/Migrations/SchoolDbContextModelSnapshot.cs b/AspNetCore.Reporting.Angular/Data/Migrations/SchoolDbContextModelSnapshot.cs deleted file mode 100644 index 2f8fa72..0000000 --- a/AspNetCore.Reporting.Angular/Data/Migrations/SchoolDbContextModelSnapshot.cs +++ /dev/null @@ -1,449 +0,0 @@ -// -using System; -using AspNetCore.Reporting.Angular.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace AspNetCore.Reporting.Angular.Data.Migrations -{ - [DbContext(typeof(SchoolDbContext))] - partial class SchoolDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.8") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("AspNetCore.Reporting.Common.Data.Course", b => - { - b.Property("CourseID") - .HasColumnType("int"); - - b.Property("Credits") - .HasColumnType("int"); - - b.Property("Title") - .HasColumnType("nvarchar(max)"); - - b.HasKey("CourseID"); - - b.ToTable("Course"); - }); - - modelBuilder.Entity("AspNetCore.Reporting.Common.Data.Enrollment", b => - { - b.Property("EnrollmentID") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("CourseID") - .HasColumnType("int"); - - b.Property("Grade") - .HasColumnType("int"); - - b.Property("StudentId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("EnrollmentID"); - - b.HasIndex("CourseID"); - - b.HasIndex("StudentId"); - - b.ToTable("Enrollment"); - }); - - modelBuilder.Entity("AspNetCore.Reporting.Common.Data.ReportEntity", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("DisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("ReportLayout") - .HasColumnType("varbinary(max)"); - - b.Property("StudentId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("ID"); - - b.HasIndex("StudentId"); - - b.ToTable("Report"); - }); - - modelBuilder.Entity("AspNetCore.Reporting.Common.Data.StudentIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("EnrollmentDate") - .HasColumnType("datetime2"); - - b.Property("FirstMidName") - .HasColumnType("nvarchar(max)"); - - b.Property("LastName") - .HasColumnType("nvarchar(max)"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("Student"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasMaxLength(50000); - - b.Property("DeviceCode") - .IsRequired() - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); - - b.Property("Expiration") - .IsRequired() - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); - - b.HasKey("UserCode"); - - b.HasIndex("DeviceCode") - .IsUnique(); - - b.HasIndex("Expiration"); - - b.ToTable("DeviceCodes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasMaxLength(50000); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); - - b.Property("Type") - .IsRequired() - .HasColumnType("nvarchar(50)") - .HasMaxLength(50); - - b.HasKey("Key"); - - b.HasIndex("Expiration"); - - b.HasIndex("SubjectId", "ClientId", "Type"); - - b.ToTable("PersistedGrants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Name") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("AspNetCore.Reporting.Common.Data.Enrollment", b => - { - b.HasOne("AspNetCore.Reporting.Common.Data.Course", "Course") - .WithMany("Enrollments") - .HasForeignKey("CourseID"); - - b.HasOne("AspNetCore.Reporting.Common.Data.StudentIdentity", "Student") - .WithMany("Enrollments") - .HasForeignKey("StudentId"); - }); - - modelBuilder.Entity("AspNetCore.Reporting.Common.Data.ReportEntity", b => - { - b.HasOne("AspNetCore.Reporting.Common.Data.StudentIdentity", "Student") - .WithMany() - .HasForeignKey("StudentId"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("AspNetCore.Reporting.Common.Data.StudentIdentity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("AspNetCore.Reporting.Common.Data.StudentIdentity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("AspNetCore.Reporting.Common.Data.StudentIdentity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("AspNetCore.Reporting.Common.Data.StudentIdentity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/AspNetCore.Reporting.Angular/Program.cs b/AspNetCore.Reporting.Angular/Program.cs deleted file mode 100644 index 2f642f0..0000000 --- a/AspNetCore.Reporting.Angular/Program.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using AspNetCore.Reporting.Angular.Data; -using AspNetCore.Reporting.Common.Data; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace AspNetCore.Reporting.Angular { - public class Program { - public static void Main(string[] args) { - var host = CreateHostBuilder(args).Build(); - InitializeDb(host); - host.Run(); - } - static void InitializeDb(IHost host) { - using(var scope = host.Services.CreateScope()) { - var services = scope.ServiceProvider; - try { - var context = services.GetRequiredService(); - var userManager = services.GetRequiredService>(); - DbInitializer.Initialize(context, userManager, new Common.Reports.ReportsFactory()); - } catch(Exception exception) { - var logger = services.GetRequiredService>(); - logger.LogError(exception, "An error occurred while seeding the database."); - } - } - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); - }); - } -} diff --git a/AspNetCore.Reporting.Angular/Properties/launchSettings.json b/AspNetCore.Reporting.Angular/Properties/launchSettings.json deleted file mode 100644 index a7c0d12..0000000 --- a/AspNetCore.Reporting.Angular/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51510", - "sslPort": 44360 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "AspNetCore.Reporting.Angular": { - "commandName": "Project", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/AspNetCore.Reporting.Angular/Startup.cs b/AspNetCore.Reporting.Angular/Startup.cs deleted file mode 100644 index 423d73c..0000000 --- a/AspNetCore.Reporting.Angular/Startup.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Linq; -using AspNetCore.Reporting.Angular.Data; -using AspNetCore.Reporting.Common.Data; -using AspNetCore.Reporting.Common.Services; -using AspNetCore.Reporting.Common.Services.Reporting; -using DevExpress.AspNetCore; -using DevExpress.AspNetCore.Reporting; -using DevExpress.Utils; -using DevExpress.XtraReports.Web.Extensions; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.SpaServices.AngularCli; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace AspNetCore.Reporting.Angular -{ - public class Startup { - public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) { - Configuration = configuration; - WebHostEnvironment = webHostEnvironment; - } - - public IConfiguration Configuration { get; } - public IWebHostEnvironment WebHostEnvironment { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) { - services.AddDevExpressControls(); - services.AddDbContext(options => - options - //.UseSqlServer(Configuration.GetConnectionString("DefaultMSSqlConnection"))); - .UseSqlite(Configuration.GetConnectionString("DefaultSqliteConnection"))); - - services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) - .AddUserManager>() - .AddEntityFrameworkStores(); - - services.AddIdentityServer() - .AddApiAuthorization(x => { - var api = x.ApiResources.FirstOrDefault(); - api.UserClaims = new[] { System.Security.Claims.ClaimTypes.Sid }; - }); - - services.AddAuthentication() - .AddIdentityServerJwt(); - - services.AddControllersWithViews().AddNewtonsoftJson(); - services.AddRazorPages(); - services.ConfigureReportingServices(x => { - if (WebHostEnvironment.IsDevelopment()) { - x.UseDevelopmentMode(); - } - x.ConfigureReportDesigner(reportDesignerConfigurator => { - reportDesignerConfigurator.RegisterObjectDataSourceWizardTypeProvider(); - }); - }); - ServiceRegistrator.AddCommonServices(services, WebHostEnvironment.ContentRootPath); - - services.AddSingleton, ScopedDbContextProvider>(); - services.AddScoped>(); - services.AddTransient>(); - services.AddTransient(); - DeserializationSettings.RegisterTrustedClass(typeof(CourseListReportRepository)); - services.AddTransient(); - DeserializationSettings.RegisterTrustedClass(typeof(MyEnrollmentsReportRepository)); - - // In production, the Angular files will be served from this directory - services.AddSpaStaticFiles(configuration => { - configuration.RootPath = "ClientApp/dist"; - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - app.UseDevExpressControls(); - if(env.IsDevelopment()) { - app.UseDeveloperExceptionPage(); - } else { - app.UseExceptionHandler("/Error"); - // 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(); - if(!env.IsDevelopment()) { - app.UseSpaStaticFiles(); - } - - app.UseRouting(); - - app.UseAuthentication(); - app.UseIdentityServer(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller}/{action=Index}/{id?}"); - endpoints.MapRazorPages(); - }); - - app.UseSpa(spa => { - // To learn more about options for serving an Angular SPA from ASP.NET Core, - // see https://go.microsoft.com/fwlink/?linkid=864501 - - spa.Options.SourcePath = "ClientApp"; - - if(env.IsDevelopment()) { - spa.UseAngularCliServer(npmScript: "start"); - } - }); - } - } -} diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Document/0.prnx b/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Document/0.prnx deleted file mode 100644 index 61aee6d..0000000 Binary files a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Document/0.prnx and /dev/null differ diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/ExportInfo/0.prnx b/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/ExportInfo/0.prnx deleted file mode 100644 index 4a11449..0000000 Binary files a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/ExportInfo/0.prnx and /dev/null differ diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/ExportInfo/1.prnx b/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/ExportInfo/1.prnx deleted file mode 100644 index d51f4b7..0000000 Binary files a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/ExportInfo/1.prnx and /dev/null differ diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/0.prnx b/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/0.prnx deleted file mode 100644 index 86e32f2..0000000 --- a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/0.prnx +++ /dev/null @@ -1 +0,0 @@ -{"BuilderId":"2f413f66b4ea452c8faeef6fb5d2a019","Finished":false,"StartBuildTimeUtc":"\/Date(1639660581715)\/"} \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/1.prnx b/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/1.prnx deleted file mode 100644 index a73b30f..0000000 --- a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/1.prnx +++ /dev/null @@ -1 +0,0 @@ -{"BuilderId":"2f413f66b4ea452c8faeef6fb5d2a019","Finished":true} \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/2.prnx b/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/2.prnx deleted file mode 100644 index 74d0d57..0000000 --- a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Metadata/2.prnx +++ /dev/null @@ -1 +0,0 @@ -{"BuilderId":"2f413f66b4ea452c8faeef6fb5d2a019","DocumentDetails":{"canPerformContinuousExport":true,"drillDownKeys":[],"errors":[],"exportOptions":"{\"Html\":{\"@PageBorderColor\":\"Black\",\"@Title\":\"NewReport\",\"@EmbedImagesInHTML\":\"true\"},\"Mht\":{\"@PageBorderColor\":\"Black\",\"@Title\":\"NewReport\"},\"PrintPreview\":{\"@DefaultFileName\":\"NewReport\"},\"MailMessage\":{\"@PageBorderColor\":\"Black\",\"@Title\":\"NewReport\"}}","pageCount":1,"sortingState":[]},"Finished":true} \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Page/0.prnx b/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Page/0.prnx deleted file mode 100644 index 2d1c77e..0000000 --- a/AspNetCore.Reporting.Angular/ViewerStorages/Documents/803b2d4fd99079e7b7752d60b30a62b3/Page/0.prnx +++ /dev/null @@ -1,6 +0,0 @@ -Xn6}/ ,xt d Nǂ6hPʮEl Q%T(6m$љ9s8C/_ޮ)*2[C-D&"bډoFGƠOgP:%#πLd<-o!+ ^P. ,leJL>OC` ѻ%\,e. -cyN+xj Z@7`f+~1X<^H}ۍ,ȸ -Q$XVhCd. +r|Rvh\<@|a(Swg+4@*t Y ϵmT$QWuv}v)̍a&<"cX{&6&~@N\clK7)@W1ə˹TTb<#ryS}%nkEДK(НyZ;sez_O;\DnPycMk.N^Q0'BהVI%[yN#vFsɧOzS3ubCg24 -P[Ǐ9:T5~taaoT?sQQ^anPפzf`vU.з0x&706} }yAyoπ>Oa3ad7:̖GVhպ ^$nY>4]➤ҡ;hW\hѡSu3\չԾ;=I0šܕez|}PPo_vh+n0t-s纕 ԥ!ѷX"ԯh|eme^VusvU\=։6c='Ƶ#Cq'Eݠ6j V3a/*fκmȷqz5jDF=caI9 -M9Sũy|ej_CxD~į}ONo}l7[G1://'S OK,c_{ -/ \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Reports/d3b31846ec8db38d248596e6af17c743/reportDetails.json b/AspNetCore.Reporting.Angular/ViewerStorages/Reports/d3b31846ec8db38d248596e6af17c743/reportDetails.json deleted file mode 100644 index ccb2076..0000000 --- a/AspNetCore.Reporting.Angular/ViewerStorages/Reports/d3b31846ec8db38d248596e6af17c743/reportDetails.json +++ /dev/null @@ -1 +0,0 @@ -{"isCachedReportSource":true,"reportUrl":"1"} \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/ViewerStorages/Reports/d3b31846ec8db38d248596e6af17c743/virtualReport.repx b/AspNetCore.Reporting.Angular/ViewerStorages/Reports/d3b31846ec8db38d248596e6af17c743/virtualReport.repx deleted file mode 100644 index 3326dc0..0000000 --- a/AspNetCore.Reporting.Angular/ViewerStorages/Reports/d3b31846ec8db38d248596e6af17c743/virtualReport.repx +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/AspNetCore.Reporting.Angular/appsettings.json b/AspNetCore.Reporting.Angular/appsettings.json deleted file mode 100644 index c04b784..0000000 --- a/AspNetCore.Reporting.Angular/appsettings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ConnectionStrings": { - "DefaultMSSqlConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-AspNetCore.Reporting.Angular-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true", - "DefaultSqliteConnection": "Data Source=Database/application.db" - }, - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "IdentityServer": { - "Clients": { - "AspNetCore.Reporting.Angular": { - "Profile": "IdentityServerSPA" - } - } - }, - "AllowedHosts": "*" -} diff --git a/Readme.md b/Readme.md index 4e3bb2d..f585623 100644 --- a/Readme.md +++ b/Readme.md @@ -54,14 +54,14 @@ To run the example application, you need to install packages from the DevExpress - For the **ASP.NET Core MVC** project, run `npm install` in the project's root folder. - For the **Angular** project, navigate to the **ClientApp** directory and run `npm install`. -### Start the Application - > **Note:** If you change the version of DevExpress NuGet packages used in the example application, make sure you also specify the matching minor versions for DevExpress client libraries in the **package.json** file. +### Start the Application + Press the **Run** button or F5 to run the example application. -![Best Pracices for Web Reporting App](Images/screenshot.png) +![Best Practices for Web Reporting App](Images/screenshot.png) ## Switch to Asynchronous Mode @@ -105,9 +105,10 @@ To optimize memory consumption, use the following techniques: [DisplayReport.cshtml](AspNetCore.Reporting.MVC/Views/Home/DisplayReport.cshtml#L9) ```js function WebDocumentViewer_BeforeRender(s, e) { - $(window).on('beforeunload', function(e) { - s.Close(); - }); + $(window).on('beforeunload', function(e) { + s.Close(); + }); + } ``` - Configure Storage and Cache cleaners on application startup. This allows you to specify how long you want to reserve resources to store document data on the server. Note that after a document's data is removed for the Storage and Cache, you cannot navigate or print this document. @@ -562,9 +563,9 @@ A translation does not always cover all the possible strings. You can use the lo For a full code example, refer to the example project's [Views/Home/DesignReport.cshtml](AspNetCore.Reporting.MVC/Views/Home/DesignReport.cshtml) or [Views/Home/DisplayReport.cshtml](AspNetCore.Reporting.MVC/Views/Home/DisplayReport.cshtml). -## Does this example address your development requirements/objectives? - -[](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=AspNetCore.Reporting.BestPractices&~~~was_helpful=yes) [](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=AspNetCore.Reporting.BestPractices&~~~was_helpful=no) - +## Does this example address your development requirements/objectives? + +[](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=AspNetCore.Reporting.BestPractices&~~~was_helpful=yes) [](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=AspNetCore.Reporting.BestPractices&~~~was_helpful=no) + (you will be redirected to DevExpress.com to submit your response)