diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json index d4e51eeadf..eb49443d6e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json @@ -8,6 +8,7 @@ "FrameworkDocumentation": "Framework documentation", "OfficialBlog": "Official blog", "CommercialHomePage": "Commercial home page", - "CommercialSupportWebSite": "Commercial support web site" + "CommercialSupportWebSite": "Commercial support web site", + "CommunityWebSite": "ABP community web site" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json index 9c36cd611b..e403e72cf6 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json @@ -8,6 +8,7 @@ "FrameworkDocumentation": "Framework Dokümantasyon", "OfficialBlog": "Resmi blog", "CommercialHomePage": "Kurumsal ana sayfa", - "CommercialSupportWebSite": "Kurumsal destek web sitesi" + "CommercialSupportWebSite": "Kurumsal destek web sitesi", + "CommunityWebSite": "ABP topluluk web sitesi" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 9e5afc3b33..a07b35c66b 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -102,7 +102,16 @@ "DoYouWantToCreateNewUser": "Do you want to create new user?", "MasterModules": "Master Modules", "OrganizationName": "Organization name", + "CreationDate": "Creation date", + "LicenseStart": "License start date", + "LicenseEndDate": "License end date", "OrganizationNamePlaceholder": "Organization name...", + "TotalQuestionCountPlaceholder": "Total question count...", + "RemainingQuestionCountPlaceholder": "Remaining question count...", + "LicenseTypePlaceholder": "License type...", + "CreationDatePlaceholder": "Creation date...", + "LicenseStartDatePlaceholder": "License start date...", + "LicenseEndDatePlaceholder": "License end date...", "UsernameOrEmail": "Username or email", "UsernameOrEmailPlaceholder": "Username or email...", "Member": "Member", @@ -156,6 +165,7 @@ "RemainingQuestionCount": "Remaining question count", "TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !", "QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !", - "UnlimitedQuestionCount": "Unlimited question count" + "UnlimitedQuestionCount": "Unlimited question count", + "Notes": "Notes" } } \ No newline at end of file diff --git a/docs/en/Modules/Identity.md b/docs/en/Modules/Identity.md index 028144c624..b950658505 100644 --- a/docs/en/Modules/Identity.md +++ b/docs/en/Modules/Identity.md @@ -1,4 +1,6 @@ # Identity Management Module +Identity module is used to manage [organization units](Organization-Units.md), roles, users and their permissions, based on the Microsoft Identity library. + See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon... diff --git a/docs/en/Modules/Index.md b/docs/en/Modules/Index.md index 71bf58333c..ae17c15c8a 100644 --- a/docs/en/Modules/Index.md +++ b/docs/en/Modules/Index.md @@ -17,7 +17,7 @@ There are some **free and open source** application modules developed and mainta * **Blogging**: Used to create fancy blogs. ABP's [own blog](https://blog.abp.io/) already using this module. * [**Docs**](Docs.md): Used to create technical documentation pages. ABP's [own documentation](https://docs.abp.io) already using this module. * **Feature Management**: Used to persist and manage the [features](../Features.md). -* **Identity**: Manages roles, users and their permissions, based on the Microsoft Identity library. +* **[Identity](Identity.md)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library. * **IdentityServer**: Integrates to IdentityServer4. * **Permission Management**: Used to persist permissions. * **[Setting Management](Setting-Management.md)**: Used to persist and manage the [settings](../Settings.md). diff --git a/docs/en/Modules/Organization-Units.md b/docs/en/Modules/Organization-Units.md new file mode 100644 index 0000000000..b27e038528 --- /dev/null +++ b/docs/en/Modules/Organization-Units.md @@ -0,0 +1,47 @@ +# Organization Unit Management + +Organization units (OU) is a part of **Identity Module** and can be used to **hierarchically group users and entities**. + +### OrganizationUnit Entity + +An OU is represented by the **OrganizationUnit** entity. The fundamental properties of this entity are: + +- **TenantId**: Tenant's Id of this OU. Can be null for host OUs. +- **ParentId**: Parent OU's Id. Can be null if this is a root OU. +- **Code**: A hierarchical string code that is unique for a tenant. +- **DisplayName**: Shown name of the OU. + +The OrganizationUnit entity's primary key (Id) is a **Guid** type and it derives from the [**FullAuditedAggregateRoot**](../Entities.md) class. + +#### Organization Tree + +Since an OU can have a parent, all OUs of a tenant are in a **tree** structure. There are some rules for this tree; + +- There can be more than one root (where the `ParentId` is `null`). +- There is a limit for the first-level children count of an OU (because of the fixed OU Code unit length explained below). + +#### OU Code + +OU code is automatically generated and maintained by the OrganizationUnit Manager. It's a string that looks something like this: + +"**00001.00042.00005**" + +This code can be used to easily query the database for all the children of an OU (recursively). There are some rules for this code: + +- It must be **unique** for a [tenant](../Multi-Tenancy.md). +- All the children of the same OU have codes that **start with the parent OU's code**. +- It's **fixed length** and based on the level of the OU in the tree, as shown in the sample. +- While the OU code is unique, it can be **changeable** if you move an OU. +- You must reference an OU by Id, not Code. + +### OrganizationUnit Manager + +The **OrganizationUnitManager** class can be [injected](../Dependency-Injection.md) and used to manage OUs. Common use cases are: + +- Create, Update or Delete an OU +- Move an OU in the OU tree. +- Getting information about the OU tree and its items. + +#### Multi-Tenancy + +The `OrganizationUnitManager` is designed to work for a **single tenant** at a time. It works for the **current tenant** by default. diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index 835c74385f..f7b062aa6e 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -754,8 +754,8 @@ It's end of this part. The final UI of this work is shown as below: It's time to create something visible and usable! There are some tools that we will use when developing ABP Angular frontend application: - [Angular CLI](https://angular.io/cli) will be used to create modules, components and services. -- [NGXS](https://ngxs.gitbook.io/ngxs/) will be used as the state management library. - [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) will be used as the UI component library. +- [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) will be used as the datatable library. - [Visual Studio Code](https://code.visualstudio.com/) will be used as the code editor (you can use your favorite editor). #### Install NPM packages @@ -778,26 +778,56 @@ yarn ng generate module book --routing true #### Routing -Open the `app-routing.module.ts` file in `src\app` folder. Add the new `import` and add a route as shown below +Open the `app-routing.module.ts` file in `src\app` folder and add a route as shown below: ```js -import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added this line to imports <== +const routes: Routes = [ +// ... +// added a new route to the routes array + { + path: 'books', + loadChildren: () => import('./book/book.module').then(m => m.BookModule) + } +] +``` -//...added books path with the below to the routes array -{ - path: 'books', - component: ApplicationLayoutComponent, - loadChildren: () => import('./book/book.module').then(m => m.BookModule), - data: { - routes: { - name: '::Menu:Books', - iconClass: 'fas fa-book' - } as ABP.Route - }, +* We added a lazy-loaded route. See the [Lazy-Loading Feature Modules](https://angular.io/guide/lazy-loading-ngmodules#lazy-loading-feature-modules). + +Open the `route.provider.ts` file in `src\app` folder and replace the content as below: + +```js +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { APP_INITIALIZER } from '@angular/core'; + +export const APP_ROUTE_PROVIDER = [ + { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, +]; + +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + //... + // added below element + { + path: '/books', + name: '::Menu:Books', + iconClass: 'fas fa-book', + order: 101, + layout: eLayoutType.application, + }, + ]); + }; } ``` -* The `ApplicationLayoutComponent` configuration sets the application layout to the new page. We added the `data` object. The `name` is the menu item name and the `iconClass` is the icon of the menu item. +* We added a new route element to show a navigation element labeled "Books" on the menu. + * `path` is the URL of the route. + * `name` is the menu item name. A Localization key can be passed. + * `iconClass` is the icon of the menu item. + * `order` is the order of the menu item. We define 101 to show the route after the "Administration" item. + * `layout` is the layout of the BooksModule's routes. `eLayoutType.application`, `eLayoutType.account` or `eLayoutType.empty` can be defined. + +For more information, see the [RoutesService document](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). #### Book list component @@ -865,35 +895,6 @@ Open the browser and navigate to http://localhost:4200/books. We'll see **book-l ![Initial book list page](./images/bookstore-initial-book-list-page.png) -#### Create BookState - -Run the following command in the terminal to create a new state, named `BooksState`: - -```bash -npx @ngxs/cli --name book --directory src/app/book -``` - -* This command creates `book.state.ts` and `book.actions.ts` files in the `src/app/book/state` folder. See the [NGXS CLI documentation](https://www.ngxs.io/plugins/cli). - -Import the `BookState` to the `app.module.ts` in the `src/app` folder and then add the `BookState` to `forRoot` static method of `NgxsModule` as an array element of the first parameter of the method. - -```js -// ... -import { BookState } from './books/state/book.state'; //<== imported BookState ==> - -@NgModule({ - imports: [ - // other imports - - NgxsModule.forRoot([BookState]), //<== added BookState ==> - - //other imports - ], - // ... -}) -export class AppModule {} -``` - #### Generate proxies ABP CLI provides `generate-proxy` command that generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running generate-proxy command, your host must be up and running. See the [CLI documentation](../CLI.md) @@ -910,109 +911,41 @@ The generated files looks like below: ![Generated files](./images/generated-proxies.png) -#### GetBooks Action - -Actions can either be thought of as a command which should trigger something to happen, or as the resulting event of something that has already happened. [See NGXS Actions documentation](https://www.ngxs.io/concepts/actions). - -Open the `book.actions.ts` file in `app/book/state` folder and replace the content below: - -```js -export class GetBooks { - static readonly type = '[Book] Get'; -} -``` - -#### Implement BookState - -Open the `book.state.ts` file in `app/book/state` folder and replace the content below: - -```js -import { PagedResultDto } from '@abp/ng.core'; -import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks } from './book.actions'; -import { BookService } from '../services'; -import { tap } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; -import { BookDto } from '../models'; - -export class BookStateModel { - public book: PagedResultDto; -} - -@State({ - name: 'BookState', - defaults: { book: {} } as BookStateModel, -}) -@Injectable() -export class BookState { - @Selector() - static getBooks(state: BookStateModel) { - return state.book.items || []; - } - - constructor(private bookService: BookService) {} - - @Action(GetBooks) - get(ctx: StateContext) { - return this.bookService.getListByInput().pipe( - tap((booksResponse) => { - ctx.patchState({ - book: booksResponse, - }); - }) - ); - } -} -``` - -* We added the book property to BookStateModel model. -* We added the `GetBooks` action that retrieves the book data via `BookService` that generated via ABP CLI and patches the state. -* `NGXS` requires to return the observable without subscribing it in the get function. - #### BookListComponent Open the `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks } from '../state/book.actions'; -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; @Component({ selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], + providers: [ListService], }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; - loading = false; - - constructor(private store: Store) {} + constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } } ``` -* We added the `get` function that updates store to get the books. -* See the [Dispatching actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions) and [Select](https://ngxs.gitbook.io/ngxs/concepts/select) on the `NGXS` documentation for more information on these `NGXS` features. +* We imported and injected the generated `BookService`. +* We implemented the [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service) that is a utility service to provide easy pagination, sorting, and search implementation. Open the `book-list.component.html` file in `app\book\book-list` folder and replace the content as below: @@ -1022,38 +955,31 @@ Open the `book-list.component.html` file in `app\book\book-list` folder and repl
- {%{{{ "::Menu:Books" | abpLocalization }}}%} + {%{{{ '::Menu:Books' | abpLocalization }}}%}
- - - - - {%{{{ "::Name" | abpLocalization }}}%} - {%{{{ "::Type" | abpLocalization }}}%} - {%{{{ "::PublishDate" | abpLocalization }}}%} - {%{{{ "::Price" | abpLocalization }}}%} - - - - - {%{{{ data.name }}}%} - {%{{{ booksType[data.type] }}}%} - {%{{{ data.publishDate | date }}}%} - {%{{{ data.price }}}%} - - + + + + + {%{{{ booksType[row.type] }}}%} + + + + + {%{{{ row.publishDate | date }}}%} + + + + + {%{{{ row.price | currency }}}%} + + +
``` @@ -1074,4 +1000,4 @@ In this tutorial we have applied the rules of official [Angular Style Guide](htt ### Next Part -See the [part 2](part-2.md) for creating, updating and deleting books. +See the [part 2](./Part-2.md) for creating, updating and deleting books. diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index 8ac9e19615..0a48866956 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -454,78 +454,49 @@ Run the application and try to delete a book. In this section, you will learn how to create a new modal dialog form to create a new book. -#### State definitions +#### Add a modal to BookListComponent -Open `book.action.ts` in `app\book\state` folder and replace the content as below: +Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: ```js -import { CreateUpdateBookDto } from '../models'; //<== added this line ==> - -export class GetBooks { - static readonly type = '[Book] Get'; -} - -// added CreateUpdateBook class -export class CreateUpdateBook { - static readonly type = '[Book] Create Update Book'; - constructor(public payload: CreateUpdateBookDto) { } -} -``` +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto, BookType } from '../models'; +import { BookService } from '../services'; -* We imported the `CreateUpdateBookDto` model and created the `CreateUpdateBook` action. +@Component({ + selector: 'app-book-list', + templateUrl: './book-list.component.html', + styleUrls: ['./book-list.component.scss'], + providers: [ListService], +}) +export class BookListComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; -Open `book.state.ts` file in `app\book\state` folder and replace the content as below: + booksType = BookType; -```js -import { PagedResultDto } from '@abp/ng.core'; -import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks, CreateUpdateBook } from './book.actions'; // <== added CreateUpdateBook==> -import { BookService } from '../services'; -import { tap } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; -import { BookDto } from '../models'; + isModalOpen = false; // <== added this line ==> -export class BookStateModel { - public book: PagedResultDto; -} + constructor(public readonly list: ListService, private bookService: BookService) {} -@State({ - name: 'BookState', - defaults: { book: {} } as BookStateModel, -}) -@Injectable() -export class BookState { - @Selector() - static getBooks(state: BookStateModel) { - return state.book.items || []; - } + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - constructor(private bookService: BookService) {} - - @Action(GetBooks) - get(ctx: StateContext) { - return this.bookService.getListByInput().pipe( - tap((bookResponse) => { - ctx.patchState({ - book: bookResponse, - }); - }) - ); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } - // added CreateUpdateBook action listener - @Action(CreateUpdateBook) - save(ctx: StateContext, action: CreateUpdateBook) { - return this.bookService.createByInput(action.payload); + // added createBook method + createBook() { + this.isModalOpen = true; } } ``` -* We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book. +* We defined a variable called `isModalOpen` and `createBook` method. +* We added the `createBook` method. -When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`. - -#### Add a modal to BookListComponent Open `book-list.component.html` file in `books\book-list` folder and replace the content as below: @@ -534,19 +505,12 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
-
- {%{{{ '::Menu:Books' | abpLocalization }}}%} -
+
{%{{{ '::Menu:Books' | abpLocalization }}}%}
- +
- @@ -555,47 +519,40 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
- - - - - {%{{{ "::Name" | abpLocalization }}}%} - {%{{{ "::Type" | abpLocalization }}}%} - {%{{{ "::PublishDate" | abpLocalization }}}%} - {%{{{ "::Price" | abpLocalization }}}%} - - - - - {%{{{ data.name }}}%} - {%{{{ booksType[data.type] }}}%} - {%{{{ data.publishDate | date }}}%} - {%{{{ data.price }}}%} - - + + + + + {%{{{ booksType[row.type] }}}%} + + + + + {%{{{ row.publishDate | date }}}%} + + + + + {%{{{ row.price | currency }}}%} + + +
- -

{%{{{ '::NewBook' | abpLocalization }}}%}

-
+ +

{%{{{ '::NewBook' | abpLocalization }}}%}

+
- + - - - + + +
``` @@ -603,55 +560,6 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the * `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits. * We added `New book` button to the `AbpContentToolbar`. -Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: - -```js -import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../models'; -import { GetBooks } from '../state/book.actions'; -import { BookState } from '../state/book.state'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], -}) -export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; - - booksType = BookType; - - loading = false; - - isModalOpen = false; // <== added this line ==> - - constructor(private store: Store) {} - - ngOnInit() { - this.get(); - } - - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); - } - - // added createBook method - createBook() { - this.isModalOpen = true; - } -} -``` - -* We added `isModalOpen = false` and `createBook` method. - You can open your browser and click **New book** button to see the new modal. ![Empty modal for new book](./images/bookstore-empty-new-book-modal.png) @@ -663,48 +571,43 @@ You can open your browser and click **New book** button to see the new modal. Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks } from '../state/book.actions'; -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==> @Component({ selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], + providers: [ListService], }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; - loading = false; - isModalOpen = false; form: FormGroup; // <== added this line ==> - constructor(private store: Store, private fb: FormBuilder) {} // <== added FormBuilder ==> + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder // <== injected FormBuilder ==> + ) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } createBook() { - this.buildForm(); //<== added this line ==> + this.buildForm(); // <== added this line ==> this.isModalOpen = true; } @@ -799,13 +702,10 @@ export class BookModule {} Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks } from '../state/book.actions'; -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==> @@ -813,37 +713,34 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], - providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added this line ==> + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added a provide ==> }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; - //added bookTypeArr array + // <== added bookTypeArr array ==> bookTypeArr = Object.keys(BookType).filter( (bookType) => typeof this.booksType[bookType] === 'number' ); - loading = false; - isModalOpen = false; form: FormGroup; - constructor(private store: Store, private fb: FormBuilder) {} + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } createBook() { @@ -884,13 +781,10 @@ Now, you can open your browser to see the changes: Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks, CreateUpdateBook } from '../state/book.actions'; // <== added CreateUpdateBook ==> -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; @@ -898,11 +792,10 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], - providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; @@ -910,24 +803,22 @@ export class BookListComponent implements OnInit { (bookType) => typeof this.booksType[bookType] === 'number' ); - loading = false; - isModalOpen = false; form: FormGroup; - constructor(private store: Store, private fb: FormBuilder) {} + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } createBook() { @@ -950,19 +841,18 @@ export class BookListComponent implements OnInit { return; } - this.store.dispatch(new CreateUpdateBook(this.form.value)).subscribe(() => { + this.bookService.createByInput(this.form.value).subscribe(() => { this.isModalOpen = false; this.form.reset(); - this.get(); + this.list.get(); }); } } ``` -* We imported `CreateUpdateBook`. * We added `save` method -Open `book-list.component.html` in `app\book\book-list` folder and add the following `abp-button` to save the new book. +Open `book-list.component.html` in `app\book\book-list` folder, find the `` element and replace this element with the following to create a new book. ```html @@ -994,63 +884,24 @@ The final modal UI looks like below: ### Updating a book -#### CreateUpdateBook action - -Open the `book.actions.ts` in `app\book\state` folder and replace the content as below: - -```js -import { CreateUpdateBookDto } from '../models'; - -export class GetBooks { - static readonly type = '[Book] Get'; -} - -export class CreateUpdateBook { - static readonly type = '[Book] Create Update Book'; - constructor(public payload: CreateUpdateBookDto, public id?: string) {} // <== added id parameter ==> -} -``` - -* We added `id` parameter to the `CreateUpdateBook` action's constructor. - -Open the `book.state.ts` in `app\book\state` folder and replace the `save` method as below: - -```js -@Action(CreateUpdateBook) -save(ctx: StateContext, action: CreateUpdateBook) { - if (action.id) { - return this.bookService.updateByIdAndInput(action.payload, action.id); - } else { - return this.bookService.createByInput(action.payload); - } -} -``` - -#### BookListComponent - -Open `book-list.component.ts` in `app\book\book-list` folder and inject `BookService` dependency by adding it to the constructor and add a variable named `selectedBook`. +Open `book-list.component.ts` in `app\book\book-list` folder and add a variable named `selectedBook`. ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks, CreateUpdateBook } from '../state/book.actions'; -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; -import { BookService } from '../services'; // <== imported BookService ==> @Component({ selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], - providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; @@ -1058,26 +909,24 @@ export class BookListComponent implements OnInit { (bookType) => typeof this.booksType[bookType] === 'number' ); - loading = false; - isModalOpen = false; form: FormGroup; selectedBook = {} as BookDto; // <== declared selectedBook ==> - constructor(private store: Store, private fb: FormBuilder, private bookService: BookService) {} //<== injected BookService ==> + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } // <== this method is replaced ==> @@ -1109,30 +958,31 @@ export class BookListComponent implements OnInit { }); } + // <== this method is replaced ==> save() { if (this.form.invalid) { return; } - //<== added this.selectedBook.id ==> - this.store - .dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id)) - .subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.get(); - }); + // <== added request ==> + const request = this.selectedBook.id + ? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id) + : this.bookService.createByInput(this.form.value); + + request.subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); } } ``` -* We imported `BookService`. * We declared a variable named `selectedBook` as `BookDto`. -* We injected `BookService` to the constructor. `BookService` is being used to retrieve the book data which is being edited. * We added `editBook` method. This method fetches the book with the given `Id` and sets it to `selectedBook` object. * We replaced the `buildForm` method so that it creates the form with the `selectedBook` data. * We replaced the `createBook` method so it sets `selectedBook` to an empty object. -* We added `selectedBook.id` to the constructor of the new `CreateUpdateBook`. +* We replaced the `save` method. #### Add "Actions" dropdown to the table @@ -1140,27 +990,14 @@ Open the `book-list.component.html` in `app\book\book-list` folder and replace ```html
- - - - - {%{{{ "::Actions" | abpLocalization }}}%} - {%{{{ "::Name" | abpLocalization }}}%} - {%{{{ "::Type" | abpLocalization }}}%} - {%{{{ "::PublishDate" | abpLocalization }}}%} - {%{{{ "::Price" | abpLocalization }}}%} - - - - - + + + +
-
- - {%{{{ data.name }}}%} - {%{{{ booksType[data.type] }}}%} - {%{{{ data.publishDate | date }}}%} - {%{{{ data.price }}}%} - -
+
+ + + + + {%{{{ booksType[row.type] }}}%} + + + + + {%{{{ row.publishDate | date }}}%} + + + + + {%{{{ row.price | currency }}}%} + + +
``` -- We added a `th` for the "Actions" column. +- We added a `ngx-datatable-column` for the "Actions" column. - We added `button` with `ngbDropdownToggle` to open actions when clicked the button. - We have used to [NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples) for the dropdown menu of actions. @@ -1198,7 +1047,7 @@ Open `book-list.component.html` in `app\book\book-list` folder and find the ` -

{%{{{ (selectedBook.id ? 'AbpIdentity::Edit' : '::NewBook' ) | abpLocalization }}}%}

+

{%{{{ (selectedBook.id ? '::Edit' : '::NewBook' ) | abpLocalization }}}%}

``` @@ -1206,81 +1055,9 @@ Open `book-list.component.html` in `app\book\book-list` folder and find the ` -import { BookService } from '../services'; -import { tap } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; -import { BookDto } from '../models'; - -export class BookStateModel { - public book: PagedResultDto; -} - -@State({ - name: 'BookState', - defaults: { book: {} } as BookStateModel, -}) -@Injectable() -export class BookState { - @Selector() - static getBooks(state: BookStateModel) { - return state.book.items || []; - } - - constructor(private bookService: BookService) {} - - @Action(GetBooks) - get(ctx: StateContext) { - return this.bookService.getListByInput().pipe( - tap((booksResponse) => { - ctx.patchState({ - book: booksResponse, - }); - }) - ); - } - - @Action(CreateUpdateBook) - save(ctx: StateContext, action: CreateUpdateBook) { - if (action.id) { - return this.bookService.updateByIdAndInput(action.payload, action.id); - } else { - return this.bookService.createByInput(action.payload); - } - } - - // <== added DeleteBook action listener ==> - @Action(DeleteBook) - delete(ctx: StateContext, action: DeleteBook) { - return this.bookService.deleteById(action.id); - } -} -``` - -- We imported `DeleteBook` . - -- We added `DeleteBook` action listener to the end of the file. - - #### Delete confirmation popup -Open `book-list.component.ts` in`app\book\book-list` folder and inject the `ConfirmationService`. +Open `book-list.component.ts` in `app\book\book-list` folder and inject the `ConfirmationService`. Replace the constructor as below: @@ -1289,11 +1066,11 @@ import { ConfirmationService } from '@abp/ng.theme.shared'; //... constructor( - private store: Store, - private fb: FormBuilder, - private bookService: BookService, - private confirmation: ConfirmationService // <== added this line ==> -) { } + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder, + private confirmation: ConfirmationService // <== added this line ==> +) {} ``` * We imported `ConfirmationService`. @@ -1301,28 +1078,24 @@ constructor( See the [Confirmation Popup documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Confirmation-Service) -In the `book-list.component.ts` add a delete method : +In the `book-list.component.ts` add a delete method: ```js -import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/book.actions' ;// <== imported DeleteBook ==> - -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation ==> +import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation namespace ==> //... delete(id: string) { - this.confirmation - .warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure') - .subscribe(status => { - if (status === Confirmation.Status.confirm) { - this.store.dispatch(new DeleteBook(id)).subscribe(() => this.get()); - } - }); + this.confirmation.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure').subscribe((status) => { + if (status === Confirmation.Status.confirm) { + this.bookService.deleteById(id).subscribe(() => this.list.get()); + } + }); } ``` -The `delete` method shows a confirmation popup and subscribes for the user response. `DeleteBook` action dispatched only if user clicks to the `Yes` button. The confirmation popup looks like below: +The `delete` method shows a confirmation popup and subscribes for the user response. The `deleteById` method of `BookService` called only if user clicks to the `Yes` button. The confirmation popup looks like below: ![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png) @@ -1335,7 +1108,7 @@ Open `book-list.component.html` in `app\book\book-list` folder and modify the `n ```html
-
diff --git a/docs/en/Tutorials/images/bookstore-actions-buttons.png b/docs/en/Tutorials/images/bookstore-actions-buttons.png index e8243fedc7..f5d8f0870b 100644 Binary files a/docs/en/Tutorials/images/bookstore-actions-buttons.png and b/docs/en/Tutorials/images/bookstore-actions-buttons.png differ diff --git a/docs/en/Tutorials/images/bookstore-angular-file-tree.png b/docs/en/Tutorials/images/bookstore-angular-file-tree.png index ffa8dcd7e2..d921f146fc 100644 Binary files a/docs/en/Tutorials/images/bookstore-angular-file-tree.png and b/docs/en/Tutorials/images/bookstore-angular-file-tree.png differ diff --git a/docs/en/Tutorials/images/bookstore-book-list.png b/docs/en/Tutorials/images/bookstore-book-list.png index d402895c9b..ecdb87c737 100644 Binary files a/docs/en/Tutorials/images/bookstore-book-list.png and b/docs/en/Tutorials/images/bookstore-book-list.png differ diff --git a/docs/en/Tutorials/images/bookstore-confirmation-popup.png b/docs/en/Tutorials/images/bookstore-confirmation-popup.png index a80b180f1c..b17e70638d 100644 Binary files a/docs/en/Tutorials/images/bookstore-confirmation-popup.png and b/docs/en/Tutorials/images/bookstore-confirmation-popup.png differ diff --git a/docs/en/Tutorials/images/bookstore-empty-new-book-modal.png b/docs/en/Tutorials/images/bookstore-empty-new-book-modal.png index 2a02802bb9..f4e969ae22 100644 Binary files a/docs/en/Tutorials/images/bookstore-empty-new-book-modal.png and b/docs/en/Tutorials/images/bookstore-empty-new-book-modal.png differ diff --git a/docs/en/UI/Angular/List-Service.md b/docs/en/UI/Angular/List-Service.md index b3c8571866..f65f0db4cd 100644 --- a/docs/en/UI/Angular/List-Service.md +++ b/docs/en/UI/Angular/List-Service.md @@ -53,59 +53,41 @@ class BookComponent { } ``` -> Noticed `list` is `public` and `readonly`? That is because we will use `ListService` properties directly in the component's template. That may be considered as an anti-pattern, but it is much quicker to implement. You can always use public component properties instead. +> Noticed `list` is `public` and `readonly`? That is because we will use `ListService` directly in the component's template. That may be considered as an anti-pattern, but it is much quicker to implement. You can always use public component members to expose the `ListService` instance instead. -Place `ListService` properties into the template like this: +Bind `ListService` to ngx-datatable like this: ```html - - - - - - - {%{{{ '::Name' | abpLocalization }}}%} - - - - - - - - {%{{{ data.name }}}%} - - + + ``` + ## Usage with Observables You may use observables in combination with [AsyncPipe](https://angular.io/guide/observables-in-angular#async-pipe) of Angular instead. Here are some possibilities: -```ts +```js book$ = this.list.hookToQuery(query => this.bookService.getListByInput(query)); ``` ```html - - + + ``` @@ -113,7 +95,7 @@ You may use observables in combination with [AsyncPipe](https://angular.io/guide ...or... -```ts +```js @Select(BookState.getBooks) books$: Observable; @@ -128,32 +110,41 @@ You may use observables in combination with [AsyncPipe](https://angular.io/guide ```html - - + + ``` +> We do not recommend using NGXS store for CRUD pages, unless your application needs to share list information between components or use it later on in another page. + + ## How to Refresh Table on Create/Update/Delete `ListService` exposes a `get` method to trigger a request with the current query. So, basically, whenever a create, update, or delete action resolves, you can call `this.list.get();` and it will call hooked stream creator again. -```ts -this.store.dispatch(new DeleteBook(id)).subscribe(this.list.get); +```js + this.bookService.createByInput(form.value) + .subscribe(() => { + this.list.get(); + + // Other subscription logic here + }); ``` ...or... -```ts -this.bookService.createByInput(form.value) - .subscribe(() => { - this.list.get(); - - // Other subscription logic here - }) +```js + this.store.dispatch(new DeleteBook(id)).subscribe(this.list.get); ``` +> We donot recommend using NGXS store for CRUD pages, unless your application needs to share list information between components or use it later on in another page. + + ## How to Implement Server-Side Search in a Table `ListService` exposes a `filter` property that will trigger a request with the current query and the given search string. All you need to do is to bind it to an input element with two-way binding. @@ -163,3 +154,26 @@ this.bookService.createByInput(form.value) ``` + +## Breaking Change with ABP v3.0 + +We had to modify the `ListService` to make it work with `ngx-datatable`. Previously, the minimum value for `page` property was `1` and you could use it like this: + +```html + + +``` + +As of v3.0, with ngx-datatable, the `page` property has to be set as `0` for inital page. Therefore, if you used `ListService` on your tables before and are going to keep `abp-table`, you need to make the following change: + +```html + + +``` + +**Important Note:** The `abp-table` is not removed, but is deprecated and will be removed in the future. Please consider switching to ngx-datatable. diff --git a/docs/en/UI/Angular/Migration-Guide-v3.md b/docs/en/UI/Angular/Migration-Guide-v3.md new file mode 100644 index 0000000000..8c1a16c942 --- /dev/null +++ b/docs/en/UI/Angular/Migration-Guide-v3.md @@ -0,0 +1,370 @@ +# v2.9 to v3.0 Angular UI Migration Guide + +## What Changed in v3.0? + +### Angular 10 + +The new ABP Angular UI is based on Angular 10 and TypeScript 3.9, and we have dropped support for Angular 8. Nevertheless, ABP modules will keep working with Angular 9. Therefore, if your project is Angular 9, you do not need to update to Angular 10. The update is usually very easy though. + +#### What to Do When Migrating? + +Open a terminal at your root folder and run the following command: + +```sh +yarn ng update @angular/cli @angular/core --force +``` + +This will make the following modifications: + +- Update your package.json and install new packages +- Revise tsconfig.json files to create a "Solution Style" configuration +- Rename `browserlist` as `.browserlistrc` + +On the other hand, it would be better if you check which packages to update first with `yarn ng update` command alone. Angular will give you a list of packages to update. + +![Table of packages to update](./images/table-of-packages-to-update.png) + +When Angular reports the packages above, your command would look like this: + +```sh +yarn ng update @angular/cli @angular/core ng-zorro-antd --force +``` + +> If Angular complains about uncommited changes in your repo, you can either commit/stash them or add `--allow-dirty` parameter to the command. + + +### Config Modules + +In ABP v2.x, every lazy loaded module had a config module available via a separate package and module configuration was as follows: + +```js +import { AccountConfigModule } from '@abp/ng.account.config'; + +@NgModule({ + imports: [ + // other imports + AccountConfigModule.forRoot({ redirectUrl: '/' }), + ], + // providers, declarations, and bootstrap +}) +export class AppModule {} +``` + +...and in app-routing.module.ts... + +```js +const routes: Routes = [ + // other route configuration + { + path: 'account', + loadChildren: () => import( + './lazy-libs/account-wrapper.module' + ).then(m => m.AccountWrapperModule), + }, +]; +``` + +Although working, this had a few disadvantages: + +- Every module came in two independent packages, but in reality, those packages were interdependent. +- Configuring lazy loaded modules required a wrapper module. +- ABP Commercial had extensibility system and configuring extensible modules at the root module was increasing the bundle size. + +In ABP v3.0, we have introduced a secondary entry points for each config module as well as a new way to configure lazy loaded modules without the wrappers. Now, the module configuration looks like this: + +```js +import { AccountConfigModule } from '@abp/ng.account/config'; + +@NgModule({ + imports: [ + // other imports + AccountConfigModule.forRoot(), + ], + // providers, declarations, and bootstrap +}) +export class AppModule {} +``` + +...and in app-routing.module.ts... + +```js +const routes: Routes = [ + // other route configuration + { + path: 'account', + loadChildren: () => import('@abp/ng.account') + .then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), + }, +]; +``` + +This change helped us reduce bundle size and build times substantially. We believe you will notice the difference in your apps. + +#### A Better Example + +AppModule: + +```js +import { AccountConfigModule } from '@abp/ng.account/config'; +import { CoreModule } from '@abp/ng.core'; +import { IdentityConfigModule } from '@abp/ng.identity/config'; +import { SettingManagementConfigModule } from '@abp/ng.setting-management/config'; +import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config'; +import { ThemeBasicModule } from '@abp/ng.theme.basic'; +import { ThemeSharedModule } from '@abp/ng.theme.shared'; +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NgxsModule } from '@ngxs/store'; +import { environment } from '../environments/environment'; +import { AppRoutingModule } from './app-routing.module'; + +@NgModule({ + imports: [ + BrowserModule, + BrowserAnimationsModule, + AppRoutingModule, + CoreModule.forRoot({ + environment, + sendNullsAsQueryParam: false, + skipGetAppConfiguration: false, + }), + ThemeSharedModule.forRoot(), + AccountConfigModule.forRoot(), + IdentityConfigModule.forRoot(), + TenantManagementConfigModule.forRoot(), + SettingManagementConfigModule.forRoot(), + ThemeBasicModule.forRoot(), + NgxsModule.forRoot(), + ], + // providers, declarations, and bootstrap +}) +export class AppModule {} +``` + +AppRoutingModule: + +```js +import { DynamicLayoutComponent } from '@abp/ng.core'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: '', + component: DynamicLayoutComponent, + children: [ + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./home/home.module') + .then(m => m.HomeModule), + }, + { + path: 'account', + loadChildren: () => import('@abp/ng.account') + .then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), + }, + { + path: 'identity', + loadChildren: () => import('@abp/ng.identity') + .then(m => m.IdentityModule.forLazy()), + }, + { + path: 'tenant-management', + loadChildren: () => import('@abp/ng.tenant-management') + .then(m => m.TenantManagementModule.forLazy()), + }, + { + path: 'setting-management', + loadChildren: () => import('@abp/ng.setting-management') + .then(m => m.SettingManagementModule.forLazy()), + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} +``` + +> You may have noticed that we used `DynamicLayoutComponent` at top level route component. We made this change in order to avoid unnecessary renders and flickering. It is not mandatory, but we recommend doing the same in your app routing. + +#### What to Do When Migrating? + +- Remove config packages from your project using `yarn remove`. +- Import config modules from secondary entry points (e.g. `@abp/ng.identity/config`). +- Call static `forRoot` method of all new config modules, even if a configuration is not passed. +- Call static `forRoot` method of `ThemeBasicModule` (or `ThemeLeptonModule` if commercial) and remove `SharedModule` from imports (unless you have added anything that is necessary for your root module in it). +- Import lazy ABP modules directly in app routing module (e.g. `() => import('@abp/ng.identity').then(...)`). +- Call static `forLazy` method of all lazy modules inside `then`, even if a configuration is not passed. +- [OPTIONAL] Add an empty parent route with `DynamicLayoutComponent` for better performance and UX. + + +### RoutesService + +In ABP v2.x, adding a route to the menu was done by one of two ways: + +- [Via `routes` Property in `AppRoutingModule`](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#via-routes-property-in-approutingmodule) +- [Via ConfigState](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#via-configstate) + +As of v3.0, we have changed how routes are added and modified. We are no longer storing routes in `ConfigState` (breaking change). Instead, there is a new service called `RoutesService` which is used for adding, patching, or removing menu items. Please check [the documentation](./Modifying-the-Menu.md) for details. + +#### What to Do When Migrating? + +- Check if you have ever used `ConfigState` or `ConfigStateService` to add any routes. If so, replace them with `add` method of `RoutesService`. +- Check if you have ever patched a route. If so, convert them to `patch` method of `RoutesService`. +- Double-check that you are using absolute paths and providing a `parentName` instead of `children` property for sub-menu items in `add` or `patch` method calls. + + +### NavItemsService + +In ABP v2.x, adding a navigation element was done [via LayoutStateService](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#how-to-add-an-element-to-right-part-of-the-menu) + +As of v3.0, we have changed how navigation items are added and modified and previous method of doing so is no longer available (breaking change). Please check [the documentation](./Modifying-the-Menu.md) for details. + +#### What to Do When Migrating? + +- Replace all `dispatchAddNavigationElement` calls with `addItems` method of `NavItemsService`. + + +### ngx-datatable + +Until v3, we had been using a custom component, `abp-table`, as the default table. However, data grids are complicated components and implementing a fully-featured one requires considerable effort, which we are planning to put in to other features and issues. + +As of ABP v3, we have switched to a battle-tested, well-executed data grid: [ngx-datatable](https://github.com/swimlane/ngx-datatable). All ABP modules will come with ngx-datatable already implemented in them. `ThemeSharedModule` already exports `NgxDatatableModule`. So, if you install the package by running `yarn add @swimlane/ngx-datatable` in your terminal, it will be available for use in all modules of your app. + +For proper styling, you need to add the following in the styles section of your angular.json file: + +```json + { + "input": "node_modules/@swimlane/ngx-datatable/index.css", + "inject": true, + "bundleName": "ngx-datatable-index" + }, + { + "input": "node_modules/@swimlane/ngx-datatable/assets/icons.css", + "inject": true, + "bundleName": "ngx-datatable-icons" + }, + { + "input": "node_modules/@swimlane/ngx-datatable/themes/material.css", + "inject": true, + "bundleName": "ngx-datatable-material" + } +``` + +Since `abp-table` is not dropped yet, modules previously built by ABP v2.x will not suddenly lose all their tables. Yet, they will look and feel different from built-in ABP v3 modules. Therefore, you will probably want to convert the tables in those modules to ngx-datatable. In order to decrease the amount of work required to convert an abp-table into ngx-datatable, we have modified the [ListService](./List-Service.md) to work well with ngx-datatable and [introduced](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates) two new directives: `NgxDatatableListDirective` and `NgxDatatableDefaultDirective`. + +The usage of those directives is rather simple: + +```js +@Component({ + providers: [ListService], +}) +export class SomeComponent { + data$ = this.list.hookToQuery( + query => this.dataService.get(query) + ); + + constructor( + public readonly list: ListService, + public readonly dataService: SomeDataService, + ) {} +} +``` + +...and in component template... + +```html + + + +``` + +Once you bind the injected `ListService` instance through `NgxDatatableListDirective`, you no longer need to worry about pagination or sorting. Similarly, `NgxDatatableDefaultDirective` gets rid of several property bindings to make ngx-datatable fit our styles. + +#### A Better Example + +```html + + + + + + + + + + + + + + +
{%{{{ row.someOtherProp }}}%}
+
+
+
+``` + +#### What to Do When Migrating? + +- Install `@swimlane/ngx-datatable` package. +- Add ngx-datatable styles in the angular.json file. +- If you can, update your modules according to the example above. +- If you have to do that later and are planning to keep abp-table for a while, make sure you update your pagination according to the [breaking change described here](./List-Service.md). + +**Important Note:** The `abp-table` is not removed, but is deprecated and will be removed in the future. Please consider switching to ngx-datatable. + + +### Deprecated Interfaces + +Some interfaces have long been marked as deprecated and now they are removed. + +#### What to Do When Migrating? + +- Please check if you are still using [anything listed in this issue](https://github.com/abpframework/abp/issues/4281) + + +## What's Next? + +* [Service Proxies](./Service-Proxies.md) + diff --git a/docs/en/UI/Angular/Modifying-the-Menu.md b/docs/en/UI/Angular/Modifying-the-Menu.md index 450a5aecd1..76298e1674 100644 --- a/docs/en/UI/Angular/Modifying-the-Menu.md +++ b/docs/en/UI/Angular/Modifying-the-Menu.md @@ -25,9 +25,99 @@ export const environment = { ## How to Add a Navigation Element +### Via `RoutesService` + +You can add routes to the menu by calling the `add` method of `RoutesService`. It is a singleton service, i.e. provided in root, so you can inject and use it immediately. + +```js +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { Component } from '@angular/core'; + +@Component(/* component metadata */) +export class AppComponent { + constructor(routes: RoutesService) { + routes.add([ + { + path: '/your-path', + name: 'Your navigation', + order: 101, + iconClass: 'fas fa-question-circle', + requiredPolicy: 'permission key here', + layout: eLayoutType.application, + }, + { + path: '/your-path/child', + name: 'Your child navigation', + parentName: 'Your navigation', + order: 1, + requiredPolicy: 'permission key here', + }, + ]); + } +} +``` + +An alternative and probably cleaner way is to use a route provider. First create a provider: + +```js +// route.provider.ts +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { APP_INITIALIZER } from '@angular/core'; + +export const APP_ROUTE_PROVIDER = [ + { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, +]; + +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + path: '/your-path', + name: 'Your navigation', + requiredPolicy: 'permission key here', + order: 101, + iconClass: 'fas fa-question-circle', + layout: eLayoutType.application, + }, + { + path: '/your-path/child', + name: 'Your child navigation', + parentName: 'Your navigation', + requiredPolicy: 'permission key here', + order: 1, + }, + ]); + }; +} +``` + +...and then in app.module.ts... + +```js +import { NgModule } from '@angular/core'; +import { APP_ROUTE_PROVIDER } from './route.provider'; + +@NgModule({ + providers: [APP_ROUTE_PROVIDER], + // imports, declarations, and bootstrap +}) +export class AppModule {} +``` + +Here is what every property works as: + +- `path` is the absolute path of the navigation element. +- `name` is the label of the navigation element. A localization key or a localization object can be passed. +- `parentName` is a reference to the `name` of the parent route in the menu and is used for creating multi-level menu items. +- `requiredPolicy` is the permission key to access the page. See the [Permission Management document](./Permission-Management.md) +- `order` is the order of the navigation element. "Administration" has an order of `100`, so keep that in mind when ordering top level menu items. +- `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label. +- `layout` defines in which layout the route will be loaded. (default: `eLayoutType.empty`) +- `invisible` makes the item invisible in the menu. (default: `false`) + ### Via `routes` Property in `AppRoutingModule` -You can define your routes by adding `routes` as a child property to `data` property of a route configuration in the `app-routing.module`. The `@abp/ng.core` package organizes your routes and stores them in the `ConfigState`. `ApplicationLayoutComponent` gets routes from store and displays them on the menu. +You can define your routes by adding `routes` as a child property to `data` property of a route configuration in the `app-routing.module`. The `@abp/ng.core` package organizes your routes and stores them in the `RoutesService`. You can add the `routes` property like below: @@ -37,7 +127,7 @@ You can add the `routes` property like below: data: { routes: { name: 'Your navigation', - order: 3, + order: 101, iconClass: 'fas fa-question-circle', requiredPolicy: 'permission key here', children: [ @@ -48,99 +138,77 @@ You can add the `routes` property like below: requiredPolicy: 'permission key here', }, ], - } as ABP.Route, // can be imported from @abp/ng.core - } + }, + }, } ``` -- `name` is the label of the navigation element. A localization key or a localization object can be passed. -- `order` is the order of the navigation element. -- `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label. -- `requiredPolicy` is the permission key to access the page. See the [Permission Management document](./Permission-Management.md) -- `children` is an array and is used for declaring child navigation elements. The child navigation element will be placed as a child route which will be available at `'/your-path/child'` based on the given `path` property. - -After adding the `routes` property as described above, the navigation menu looks like this: - -![navigation-menu-via-app-routing](./images/navigation-menu-via-app-routing.png) - -## Via ConfigState - -The `dispatchAddRoute` method of `ConfigStateService` adds a new navigation element to the menu. +Alternatively, you can do this: ```js -// this.config is instance of ConfigStateService - -const newRoute: ABP.Route = { - name: 'My New Page', - iconClass: 'fa fa-dashboard', - path: 'page', - invisible: false, - order: 2, - requiredPolicy: 'MyProjectName.MyNewPage', -} as Omit; - -this.config.dispatchAddRoute(newRoute); -// returns a state stream which emits after dispatch action is complete -``` - -The `newRoute` will be placed as at root level, i.e. without any parent routes, and its url will be stored as `'/path'`. - -If you want **to add a child route, you can do this:** - -```js -// this.config is instance of ConfigStateService -// eIdentityRouteNames enum can be imported from @abp/ng.identity - -const newRoute: ABP.Route = { - parentName: eIdentityRouteNames.IdentityManagement, - name: 'My New Page', - iconClass: 'fa fa-dashboard', - path: 'page', - invisible: false, - order: 3, - requiredPolicy: 'MyProjectName.MyNewPage' -} as Omit; - -this.config.dispatchAddRoute(newRoute); -// returns a state stream which emits after dispatch action is complete +{ + path: 'your-path', + data: { + routes: [ + { + path: '/your-path', + name: 'Your navigation', + order: 101, + iconClass: 'fas fa-question-circle', + requiredPolicy: 'permission key here', + }, + { + path: '/your-path/child', + name: 'Your child navigation', + parentName: 'Your navigation', + order: 1, + requiredPolicy: 'permission key here', + }, + ] as ABP.Route[], // can be imported from @abp/ng.core + }, +} ``` -The `newRoute` will then be placed as a child of the parent route named `eIdentityRouteNames.IdentityManagement` and its url will be set as `'/identity/page'`. +The advantage of the second method is that you are not bound to the parent/child structure and use any paths you like. -The new route will be added like below: +After adding the `routes` property as described above, the navigation menu looks like this: -![navigation-menu-via-config-state](./images/navigation-menu-via-config-state.png) +![navigation-menu-via-app-routing](./images/navigation-menu-via-app-routing.png) -## How to Patch a Navigation Element +## How to Patch or Remove a Navigation Element -The `dispatchPatchRouteByName` method finds a route by its name and replaces its configuration in the store with the new configuration passed as the second parameter. +The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children. ```js -// this.config is instance of ConfigStateService -// eIdentityRouteNames enum can be imported from @abp/ng.identity +// this.routes is instance of RoutesService +// eThemeSharedRouteNames enum can be imported from @abp/ng.theme.shared + +const dashboardRouteConfig: ABP.Route = { + path: '/dashboard', + name: '::Menu:Dashboard', + parentName: '::Menu:Home', + order: 1, + layout: eLayoutType.application, +}; -const newRouteConfig: Partial = { +const newHomeRouteConfig: Partial = { iconClass: 'fas fa-home', - parentName: eIdentityRouteNames.Administration, + parentName: eThemeSharedRouteNames.Administration, order: 0, - children: [ - { - name: 'Dashboard', - path: 'dashboard', - }, - ], }; -this.config.dispatchPatchRouteByName('::Menu:Home', newRouteConfig); -// returns a state stream which emits after dispatch action is complete +this.routes.add([dashboardRouteConfig]); +this.routes.patch('::Menu:Home', newHomeRouteConfig); +this.routes.remove(['Your navigation']); ``` -* Moved the _Home_ navigation under the _Administration_ dropdown based on given `parentName`. -* Added an icon. -* Specified the order. -* Added a child route named _Dashboard_. +- Moved the _Home_ navigation under the _Administration_ dropdown based on given `parentName`. +- Added an icon to _Home_. +- Specified the order and made _Home_ the first item in list. +- Added a route named _Dashboard_ as a child of _Home_. +- Removed _Your navigation_ along with its child route. -After the patch above, navigation elements looks like below: +After the operations above, the new menu looks like below: ![navigation-menu-after-patching](./images/navigation-menu-after-patching.png) diff --git a/docs/en/UI/Angular/images/table-of-packages-to-update.png b/docs/en/UI/Angular/images/table-of-packages-to-update.png new file mode 100644 index 0000000000..fc492d3740 Binary files /dev/null and b/docs/en/UI/Angular/images/table-of-packages-to-update.png differ diff --git a/docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md b/docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md index 5b4b766a27..3992c7d8a1 100644 --- a/docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md +++ b/docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md @@ -11,92 +11,95 @@ Basic usage: ````xml ```` + Model: + ````csharp public class DynamicFormsModel : PageModel - { - [BindProperty] - public DetailedModel MyDetailedModel { get; set; } +{ + [BindProperty] + public DetailedModel MyDetailedModel { get; set; } - public List CountryList { get; set; } = new List - { - new SelectListItem { Value = "CA", Text = "Canada"}, - new SelectListItem { Value = "US", Text = "USA"}, - new SelectListItem { Value = "UK", Text = "United Kingdom"}, - new SelectListItem { Value = "RU", Text = "Russia"} - }; + public List CountryList { get; set; } = new List + { + new SelectListItem { Value = "CA", Text = "Canada"}, + new SelectListItem { Value = "US", Text = "USA"}, + new SelectListItem { Value = "UK", Text = "United Kingdom"}, + new SelectListItem { Value = "RU", Text = "Russia"} + }; - public void OnGet() - { - MyDetailedModel = new DetailedModel - { - Name = "", - Description = "Lorem ipsum dolor sit amet.", - IsActive = true, - Age = 65, - Day = DateTime.Now, - MyCarType = CarType.Coupe, - YourCarType = CarType.Sedan, - Country = "RU", - NeighborCountries = new List() { "UK", "CA" } - }; - } + public void OnGet() + { + MyDetailedModel = new DetailedModel + { + Name = "", + Description = "Lorem ipsum dolor sit amet.", + IsActive = true, + Age = 65, + Day = DateTime.Now, + MyCarType = CarType.Coupe, + YourCarType = CarType.Sedan, + Country = "RU", + NeighborCountries = new List() { "UK", "CA" } + }; + } - public class DetailedModel - { - [Required] - [Placeholder("Enter your name...")] - [Display(Name = "Name")] - public string Name { get; set; } - - [TextArea(Rows = 4)] - [Display(Name = "Description")] - [InputInfoText("Describe Yourself")] - public string Description { get; set; } - - [Required] - [DataType(DataType.Password)] - [Display(Name = "Password")] - public string Password { get; set; } - - [Display(Name = "Is Active")] - public bool IsActive { get; set; } - - [Required] - [Display(Name = "Age")] - public int Age { get; set; } - - [Required] - [Display(Name = "My Car Type")] - public CarType MyCarType { get; set; } - - [Required] - [AbpRadioButton(Inline = true)] - [Display(Name = "Your Car Type")] - public CarType YourCarType { get; set; } - - [DataType(DataType.Date)] - [Display(Name = "Day")] - public DateTime Day { get; set; } - - [SelectItems(nameof(CountryList))] - [Display(Name = "Country")] - public string Country { get; set; } - - [SelectItems(nameof(CountryList))] - [Display(Name = "Neighbor Countries")] - public List NeighborCountries { get; set; } - } + public class DetailedModel + { + [Required] + [Placeholder("Enter your name...")] + [Display(Name = "Name")] + public string Name { get; set; } + + [TextArea(Rows = 4)] + [Display(Name = "Description")] + [InputInfoText("Describe Yourself")] + public string Description { get; set; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Display(Name = "Is Active")] + public bool IsActive { get; set; } + + [Required] + [Display(Name = "Age")] + public int Age { get; set; } + + [Required] + [Display(Name = "My Car Type")] + public CarType MyCarType { get; set; } + + [Required] + [AbpRadioButton(Inline = true)] + [Display(Name = "Your Car Type")] + public CarType YourCarType { get; set; } + + [DataType(DataType.Date)] + [Display(Name = "Day")] + public DateTime Day { get; set; } + + [SelectItems(nameof(CountryList))] + [Display(Name = "Country")] + public string Country { get; set; } + + [SelectItems(nameof(CountryList))] + [Display(Name = "Neighbor Countries")] + public List NeighborCountries { get; set; } + } - public enum CarType - { - Sedan, - Hatchback, - StationWagon, - Coupe - } + public enum CarType + { + Sedan, + Hatchback, + StationWagon, + Coupe } +} ```` + ## Demo See the [dynamic forms demo page](https://bootstrap-taghelpers.abp.io/Components/DynamicForms) to see it in action. @@ -125,7 +128,7 @@ Default value is `True`. ## Form Content Placement -By default, `abp-dynamicform` clears the inner html and places the inputs into itself. If you want to add additional content to dynamic form or place the inputs to some specific area, you can use ` ` tag. This tag will be replaced by form content and rest of the inner html of `abp-dynamic-form` tag will be unchanged. +By default, `abp-dynamic-form` clears the inner html and places the inputs into itself. If you want to add additional content to dynamic form or place the inputs to some specific area, you can use ` ` tag. This tag will be replaced by form content and rest of the inner html of `abp-dynamic-form` tag will be unchanged. Usage: @@ -152,23 +155,23 @@ Default `DisplayOrder` attribute number is 10000 for every property. See example below: ````csharp - public class OrderExampleModel - { - [DisplayOrder(10004)] - public string Name{ get; set; } - - [DisplayOrder(10005)] - public string Surname{ get; set; } - - //Default 10000 - public string EmailAddress { get; set; } - - [DisplayOrder(10003)] - public string PhoneNumber { get; set; } - - [DisplayOrder(9999)] - public string City { get; set; } - } +public class OrderExampleModel +{ + [DisplayOrder(10004)] + public string Name{ get; set; } + + [DisplayOrder(10005)] + public string Surname{ get; set; } + + //Default 10000 + public string EmailAddress { get; set; } + + [DisplayOrder(10003)] + public string PhoneNumber { get; set; } + + [DisplayOrder(9999)] + public string City { get; set; } +} ```` In this example, input fields will be displayed with this order: `City` > `EmailAddress` > `PhoneNumber` > `Name` > `Surname`. @@ -198,55 +201,57 @@ If you have read the [Form elements document](Form-elements.md), you noticed tha ````xml ```` + Model: + ````csharp public class DynamicFormsModel : PageModel - { - [BindProperty] - public DetailedModel MyDetailedModel { get; set; } +{ + [BindProperty] + public DetailedModel MyDetailedModel { get; set; } - public List CountryList { get; set; } = new List - { - new SelectListItem { Value = "CA", Text = "Canada"}, - new SelectListItem { Value = "US", Text = "USA"}, - new SelectListItem { Value = "UK", Text = "United Kingdom"}, - new SelectListItem { Value = "RU", Text = "Russia"} - }; + public List CountryList { get; set; } = new List + { + new SelectListItem { Value = "CA", Text = "Canada"}, + new SelectListItem { Value = "US", Text = "USA"}, + new SelectListItem { Value = "UK", Text = "United Kingdom"}, + new SelectListItem { Value = "RU", Text = "Russia"} + }; - public void OnGet() - { - MyDetailedModel = new DetailedModel - { - ComboCarType = CarType.Coupe, - RadioCarType = CarType.Sedan, - ComboCountry = "RU", - RadioCountry = "UK" - }; - } + public void OnGet() + { + MyDetailedModel = new DetailedModel + { + ComboCarType = CarType.Coupe, + RadioCarType = CarType.Sedan, + ComboCountry = "RU", + RadioCountry = "UK" + }; + } - public class DetailedModel - { - public CarType ComboCarType { get; set; } - - [AbpRadioButton(Inline = true)] - public CarType RadioCarType { get; set; } - - [SelectItems(nameof(CountryList))] - public string ComboCountry { get; set; } - - [AbpRadioButton()] - [SelectItems(nameof(CountryList))] - public string RadioCountry { get; set; } - } + public class DetailedModel + { + public CarType ComboCarType { get; set; } + + [AbpRadioButton(Inline = true)] + public CarType RadioCarType { get; set; } + + [SelectItems(nameof(CountryList))] + public string ComboCountry { get; set; } + + [AbpRadioButton()] + [SelectItems(nameof(CountryList))] + public string RadioCountry { get; set; } + } - public enum CarType - { - Sedan, - Hatchback, - StationWagon, - Coupe - } + public enum CarType + { + Sedan, + Hatchback, + StationWagon, + Coupe } +} ```` As you see in example above: @@ -265,13 +270,6 @@ By default, it will try to find "DisplayName:{PropertyName}" or "{PropertyName}" You can set it yourself by using `[Display()]` attribute of Asp.Net Core. You can use a localization key in this attribute. See example below: ````csharp - [Display(Name = "Name")] - public string Name { get; set; } -```` - - - - - - - +[Display(Name = "Name")] +public string Name { get; set; } +```` \ No newline at end of file diff --git a/docs/en/Unit-Of-Work.md b/docs/en/Unit-Of-Work.md index 907e601d30..743aa515bb 100644 --- a/docs/en/Unit-Of-Work.md +++ b/docs/en/Unit-Of-Work.md @@ -325,7 +325,7 @@ public async Task CreateAsync(string name) If your intent is just to save the changes after creating/updating/deleting an entity, it is suggested to use the `autoSave` option instead of manually using the `CurrentUnitOfWork.SaveChangesAsync()`. -> **Note-1**: All changes are automatically saved when a unit of work ends without any error. So, don't call `SaveChangesAsync()` unless you really need it. +> **Note-1**: All changes are automatically saved when a unit of work ends without any error. So, don't call `SaveChangesAsync()` and don't set `autoSave` to `true` unless you really need it. > > **Note-2**: If you use `Guid` as the primary key, you never need to save changes on insert to just get the generated id, because `Guid` keys are set in the application and are immediately available once you create a new entity. diff --git a/docs/en/Virtual-File-System.md b/docs/en/Virtual-File-System.md index c5a154bd3c..a519b02ec6 100644 --- a/docs/en/Virtual-File-System.md +++ b/docs/en/Virtual-File-System.md @@ -1,40 +1,30 @@ -## Virtual File System +# Virtual File System The Virtual File System makes it possible to manage files that do not physically exist on the file system (disk). It's mainly used to embed (js, css, image..) files into assemblies and use them like physical files at runtime. -### Volo.Abp.VirtualFileSystem Package +## Installation -Volo.Abp.VirtualFileSystem is the core package of the virtual file system. Install it in your project using the package manager console (PMC): +> Most of the times you don't need to manually install this package since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). -``` -Install-Package Volo.Abp.VirtualFileSystem -``` +[Volo.Abp.VirtualFileSystem](https://www.nuget.org/packages/Volo.Abp.VirtualFileSystem) is the main page of the Virtual File System. -> This package is already installed by default with the startup template. So, most of the time, you will not need to install it manually. +Use the ABP CLI to add this package to your project: -Then you can add **AbpVirtualFileSystemModule** dependency to your module: +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), if you haven't installed it. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.VirtualFileSystem` package. +* Run `abp add-package Volo.Abp.VirtualFileSystem` command. -```c# -using Volo.Abp.Modularity; -using Volo.Abp.VirtualFileSystem; +If you want to do it manually, install the [Volo.Abp.VirtualFileSystem](https://www.nuget.org/packages/Volo.Abp.VirtualFileSystem) NuGet package to your project and add `[DependsOn(typeof(AbpVirtualFileSystemModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpVirtualFileSystemModule))] - public class MyModule : AbpModule - { - //... - } -} -``` +## Working with the Embedded Files -#### Registering Embedded Files +### Embed the Files -A file should be first marked as an embedded resource to embed the file into the assembly. The easiest way to do it is to select the file from the **Solution Explorer** and set **Build Action** to **Embedded Resource** from the **Properties** window. Example: +A file should be first marked as an **embedded resource** to embed the file into the assembly. The easiest way to do it is to select the file from the **Solution Explorer** and set **Build Action** to **Embedded Resource** from the **Properties** window. Example: ![build-action-embedded-resource-sample](images/build-action-embedded-resource-sample.png) -If you want to add multiple files, this can be tedious. Alternatively, you can directly edit your **.csproj** file: +If you want to add multiple files, this can be tedious. Alternatively, you can directly edit your `.csproj` file: ````C# @@ -45,76 +35,60 @@ If you want to add multiple files, this can be tedious. Alternatively, you can d This configuration recursively adds all files under the **MyResources** folder of the project (including the files you will add in the future). -Then the module needs to be configured using `AbpVirtualFileSystemOptions` to register the embedded files to the virtual file system. Example: +Embedding a file in the project/assembly may cause problems if a file name contains some special chars. To overcome this limitation; -````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; -using Volo.Abp.VirtualFileSystem; +1. Add [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet package to the project that contains the embedded resource(s). +2. Add `true` into the `...` section of your `.csproj` file. -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpVirtualFileSystemModule))] - public class MyModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - //Register all embedded files of this assembly to the virtual file system - options.FileSets.AddEmbedded("YourRootNameSpace"); - }); +> While these two steps are optional and ABP can work without these configuration, it is strongly suggested to make it. - //... - } - } -} -```` +### Configure the AbpVirtualFileSystemOptions -The `AddEmbedded` extension method takes a class, finds all embedded files from the assembly of the given class and registers them to the virtual file system. More concisely it could be written as follows: +Use `AbpVirtualFileSystemOptions` [options class](Options.md) to register the embedded files to the virtual file system in the `ConfigureServices` method of your [module](Module-Development-Basics.md). -````C# -options.FileSets.Add( - new EmbeddedFileSet(typeof(MyModule).Assembly), "YourRootNameSpace"); +**Example: Add embedded files to the virtual file system** + +````csharp +Configure(options => +{ + options.FileSets.AddEmbedded(); +}); ```` -> "YourRootNameSpace" is the root namespace of your project. It can be empty if your root namespace is empty. +The `AddEmbedded` extension method takes a class, finds all embedded files from the **assembly of the given class** and registers them to the virtual file system. It is common to pass the module class as the generic argument. -#### Getting Virtual Files: IVirtualFileProvider +`AddEmbedded` can get two optional parameters; -After embedding a file into an assembly and registering it to the virtual file system, the `IVirtualFileProvider` interface can be used to get files or directory contents: +* `baseNamespace`: This may only needed if you didn't configure the `GenerateEmbeddedFilesManifest` step explained above and your root namespace is not empty. In this case, set your root namespace here. +* `baseFolder`: If you don't want to expose all embedded files in the project, but only want to expose a specific folder (and sub folders/files), then you can set the base folder relative to your project root page. -````C# -public class MyService +**Example: Add files under the `MyFiles` folder in the project** + +````csharp +Configure(options => { - private readonly IVirtualFileProvider _virtualFileProvider; + options.FileSets.AddEmbedded( + baseNamespace: "Acme.BookStore.MyFiles", + baseFolder: "/MyFiles" + ); +}); +```` - public MyService(IVirtualFileProvider virtualFileProvider) - { - _virtualFileProvider = virtualFileProvider; - } +This example assumes; - public void Foo() - { - //Getting a single file - var file = _virtualFileProvider.GetFileInfo("/MyResources/js/test.js"); - var fileContent = file.ReadAsString(); //ReadAsString is an extension method of ABP +* Your project root (default) namespace is `Acme.BookStore`. +* Your project has a folder, named `MyFiles` +* You only want to add `MyFiles` folder to the virtual file system. - //Getting all files/directories under a directory - var directoryContents = _virtualFileProvider.GetDirectoryContents("/MyResources/js"); - } -} -```` +### Dealing With Embedded Files During Development -#### Dealing With Embedded Files During Development - -Embedding a file into a module assembly and being able to use it from another project just by referencing the assembly (or adding a nuget package) is invaluable for creating a re-usable module. However, it does make it a little bit harder to develop the module itself. +Embedding a file into an assembly and being able to use it from another project just by referencing the assembly (or adding a NuGet package) is invaluable for creating a re-usable module. However, it makes it a little bit harder to develop the module itself. Let's assume that you're developing a module that contains an embedded JavaScript file. Whenever you change this file you must re-compile the project, re-start the application and refresh the browser page to take the change. Obviously, this is very time consuming and tedious. What is needed is the ability for the application to directly use the physical file at development time and a have a browser refresh reflect any change made in the JavaScript file. The `ReplaceEmbeddedByPhysical` method makes all this possible. -The example below shows an application that depends on a module (`MyModule`) that itself contains embedded files. The application can reach the source code of the module at development time. +The example below shows an application that depends on a module (`MyModule`) that itself contains embedded files. The application can reach the source code of the module at development time. ````C# [DependsOn(typeof(MyModule))] @@ -128,29 +102,63 @@ public class MyWebAppModule : AbpModule { Configure(options => { - //ReplaceEmbeddedByPhysical gets the root folder of the MyModule project options.FileSets.ReplaceEmbeddedByPhysical( - Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}MyModuleProject", Path.DirectorySeparatorChar)) + Path.Combine( + hostingEnvironment.ContentRootPath, + string.Format( + "..{0}MyModuleProject", + Path.DirectorySeparatorChar + ) + ) ); }); } - - //... } } ```` The code above assumes that `MyWebAppModule` and `MyModule` are two different projects in a Visual Studio solution and `MyWebAppModule` depends on the `MyModule`. -### ASP.NET Core Integration +> The [application startup template](Startup-Templates/Application.md) already uses this technique for the localization files. So, when you change a localization file it automatically detects the change. + +## IVirtualFileProvider + +After embedding a file into an assembly and registering it to the virtual file system, the `IVirtualFileProvider` interface can be used to get files or directory contents: + +````C# +public class MyService +{ + private readonly IVirtualFileProvider _virtualFileProvider; + + public MyService(IVirtualFileProvider virtualFileProvider) + { + _virtualFileProvider = virtualFileProvider; + } + + public void Foo() + { + //Getting a single file + var file = _virtualFileProvider + .GetFileInfo("/MyResources/js/test.js"); + + var fileContent = file.ReadAsString(); + + //Getting all files/directories under a directory + var directoryContents = _virtualFileProvider + .GetDirectoryContents("/MyResources/js"); + } +} +```` + +## ASP.NET Core Integration The Virtual File System is well integrated to ASP.NET Core: * Virtual files can be used just like physical (static) files in a web application. * Js, css, image files and all other web content types can be embedded into assemblies and used just like the physical files. -* An application (or another module) can override a virtual file of a module just like placing a file with the same name and extension into the same folder of the virtual file. +* An application (or another module) can **override a virtual file** of a module just like placing a file with the same name and extension into the same folder of the virtual file. -#### Virtual Files Middleware +### UseVirtualFiles Middleware The Virtual Files Middleware is used to serve embedded (js, css, image...) files to clients/browsers just like physical files in the **wwwroot** folder. Add it just after the static file middleware as shown below: @@ -160,4 +168,14 @@ app.UseVirtualFiles(); Adding virtual files middleware after the static files middleware makes it possible to override a virtual file with a real physical file simply by placing it in the same location as the virtual file. ->The Virtual File Middleware only serves the virtual wwwroot folder contents - just like the other static files. +> `UseVirtualFiles()` is already configured for the [application startup template](Startup-Templates/Application.md). + +#### Static Virtual File Folders + +By default, ASP.NET Core only allows the `wwwroot` folder to contain the static files consumed by the clients. When you use the `UseVirtualFiles` middleware, the following folders also can contain static files: + +* Pages +* Views +* Themes + +This allows to add `.js`, `.css`... files near to your `.cshtml` file that is easier to develop and maintain your project. \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index afcffe50a5..4a29b053b9 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -366,6 +366,10 @@ { "text": "Angular", "items": [ + { + "text": "Migration Guide v2.x to v3", + "path": "UI/Angular/Migration-Guide-v3.md" + }, { "text": "Service Proxies", "path": "UI/Angular/Service-Proxies.md" diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 788c4a67ed..e733b6e2e5 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -169,7 +169,8 @@ abp update [options] * `--nuget`: 仅更新的NuGet包 * `--solution-path` 或 `-sp`: 指定解决方案路径/目录. 默认使用当前目录 * `--solution-name` 或 `-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件. - +*`--check-all`: 分别检查每个包的新版本. 默认是 `false`. + ### 切换到每晚构建(预览)包 想要切换到ABP框架的最新预览版可以使用此命令. diff --git a/docs/zh-Hans/Entity-Framework-Core-MySQL.md b/docs/zh-Hans/Entity-Framework-Core-MySQL.md index 48297ea68d..75505b883d 100644 --- a/docs/zh-Hans/Entity-Framework-Core-MySQL.md +++ b/docs/zh-Hans/Entity-Framework-Core-MySQL.md @@ -8,14 +8,14 @@ ## 替换模块依赖项 -在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCoreMySQLModule)` (或者替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.MySQL;`). +在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCoreMySQLModule)` (并且替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.MySQL;`). ## UseMySQL() 查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseMySQL()`. 检查下列文件: * `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. -* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. +* `.EntityFrameworkCore.DbMigrations` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. > 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. diff --git a/docs/zh-Hans/Entity-Framework-Core-Oracle-Devart.md b/docs/zh-Hans/Entity-Framework-Core-Oracle-Devart.md new file mode 100644 index 0000000000..150ee51f11 --- /dev/null +++ b/docs/zh-Hans/Entity-Framework-Core-Oracle-Devart.md @@ -0,0 +1,60 @@ +# 切换到 EF Core Oracle Devart 提供程序 + +本文介绍如何将预配置为SqlServer提供程序的 **[应用程序启动模板](Startup-Templates/Application.md)** 切换到 **Oracle** 数据库提供程序 + +> 本文档使用[Devart](https://www.devart.com/dotconnect/oracle/)公司的付费库,有关其他选项,请参见[文档](Entity-Framework-Core-Oracle.md). + +## 替换Volo.Abp.EntityFrameworkCore.SqlServer包 + +解决方案中的 `.EntityFrameworkCore` 项目依赖于 [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet包. 删除这个包并且添加相同版本的[Volo.Abp.EntityFrameworkCore.Oracle.Devart](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) 包. + +## 替换模块依赖项 + +在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCoreOracleDevartModule)` (并且替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.Oracle.Devart;`). + +## UseOracle() + +查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseOracle()`. 检查下列文件: + +* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. +* `.EntityFrameworkCore.DbMigrations` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. + +找到 *YourProjectName*MigrationsDbContextFactory.cs 的 `CreateDbContext()` 方法,将以下代码块 + +```csharp +var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("Default")); +``` + +替换为: + +```csharp +var builder = (DbContextOptionsBuilder) + new DbContextOptionsBuilder().UseOracle + ( + configuration.GetConnectionString("Default") + ); +``` + +> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. + +## 更改连接字符串 + +Oracle连接字符串与SQL Server连接字符串不同. 所以检查你的解决方案中所有的 `appsettings.json` 文件,更改其中的连接字符串. 有关Oracle连接字符串选项的详细内容请参见[connectionstrings.com](https://www.connectionstrings.com/oracle/). + +通常需要更改 `.DbMigrator` 和 `.Web` 项目里面的 `appsettings.json` ,但它取决于你的解决方案结构. + +## 重新生成迁移 + +启动模板使用[Entity Framework Core的Code First迁移](https://docs.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/). EF Core迁移取决于所选的DBMS提供程序. 因此更改DBMS提供程序会导致迁移失败. + +* 删除 `.EntityFrameworkCore.DbMigrations` 项目下的Migrations文件夹,并重新生成解决方案. +* 在包管理控制台中运行 `Add-Migration "Initial"`(在解决方案资源管理器选择 `.DbMigrator` (或 `.Web`) 做为启动项目并且选择 `.EntityFrameworkCore.DbMigrations` 做为默认项目). + +这将创建一个配置所有数据库对象(表)的数据库迁移. + +运行 `.DbMigrator` 项目创建数据库和初始种子数据. + +## 运行应用程序 + +它已准备就绪, 只需要运行该应用程序与享受编码. diff --git a/docs/zh-Hans/Entity-Framework-Core-Oracle-Official.md b/docs/zh-Hans/Entity-Framework-Core-Oracle-Official.md new file mode 100644 index 0000000000..e5932be3db --- /dev/null +++ b/docs/zh-Hans/Entity-Framework-Core-Oracle-Official.md @@ -0,0 +1,67 @@ +# 切换到EF Core Oracle提供程序 + +本文介绍如何将预配置为SqlServer提供程序的 **[应用程序启动模板](Startup-Templates/Application.md)** 切换到 **Oracle** 数据库提供程序 + +> 本文档使用[Devart](https://www.devart.com/dotconnect/oracle/)公司的付费库,因为它是oracle唯一支持EF Core 3.x的库 + +## 替换Volo.Abp.EntityFrameworkCore.SqlServer包 + +解决方案中的 `.EntityFrameworkCore` 项目依赖于 [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet包. 删除这个包并且添加相同版本的 [Volo.Abp.EntityFrameworkCore.Oracle.Devart](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) 包. + +## 替换模块依赖项 + +在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCoreOracleDevartModule)` (并且替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.Oracle.Devart;`). + +## UseOracle() + +Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: + +* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. +* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. + +In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block + +查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseOracle()`. 检查下列文件: + +* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. +* `.EntityFrameworkCore.DbMigrations` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. + +使用以下代码替换*YourProjectName*MigrationsDbContextFactory.cs中的 `CreateDbContext()` 方法: + +```csharp +var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("Default")); +``` + +与这个 + +```csharp +var builder = (DbContextOptionsBuilder) + new DbContextOptionsBuilder().UseOracle + ( + configuration.GetConnectionString("Default") + ); +``` + +> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. + +## 更改连接字符串 + +Oracle连接字符串与SQL Server连接字符串不同. 所以检查你的解决方案中所有的 `appsettings.json` 文件,更改其中的连接字符串. 有关oracle连接字符串选项的详细内容请参见[connectionstrings.com](https://www.connectionstrings.com/oracle/). + +通常需要更改 `.DbMigrator` 和 `.Web` 项目里面的 `appsettings.json` ,但它取决于你的解决方案结构. + +## 重新生成迁移 + +启动模板使用[Entity Framework Core的Code First迁移](https://docs.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/). EF Core迁移取决于所选的DBMS提供程序. 因此更改DBMS提供程序会导致迁移失败. + +* 删除 `.EntityFrameworkCore.DbMigrations` 项目下的Migrations文件夹,并重新生成解决方案. +* 在包管理控制台中运行 `Add-Migration "Initial"`(在解决方案资源管理器选择 `.DbMigrator` (或 `.Web`) 做为启动项目并且选择 `.EntityFrameworkCore.DbMigrations` 做为默认项目). + +这将创建一个配置所有数据库对象(表)的数据库迁移. + +运行 `.DbMigrator` 项目创建数据库和初始种子数据. + +## 运行应用程序 + +它已准备就绪, 只需要运行该应用程序与享受编码. \ No newline at end of file diff --git a/docs/zh-Hans/Entity-Framework-Core-Oracle.md b/docs/zh-Hans/Entity-Framework-Core-Oracle.md index ee510e5edc..cedd4345de 100644 --- a/docs/zh-Hans/Entity-Framework-Core-Oracle.md +++ b/docs/zh-Hans/Entity-Framework-Core-Oracle.md @@ -1,67 +1,10 @@ -# 切换到EF Core Oracle提供程序 +# 切换到EF Core Oracle 提供程序 本文介绍如何将预配置为SqlServer提供程序的 **[应用程序启动模板](Startup-Templates/Application.md)** 切换到 **Oracle** 数据库提供程序 -> 本文档使用[Devart](https://www.devart.com/dotconnect/oracle/)公司的付费库,因为它是oracle唯一支持EF Core 3.x的库 +ABP框架提供了两种不同的Oracle包集成. 你可以选择以下其中一个: -## 替换Volo.Abp.EntityFrameworkCore.SqlServer包 +* **[Volo.Abp.EntityFrameworkCore.Oracle](Entity-Framework-Core-Oracle-Official.md)** 使用官方 & 免费的oracle驱动 ( **当前处于 beta**). +* **[Volo.Abp.EntityFrameworkCore.Oracle.Devart](Entity-Framework-Core-Oracle-Devart.md)** 使用[Devart](https://www.devart.com/)公司提供的商业(付费)驱动. -解决方案中的 `.EntityFrameworkCore` 项目依赖于 [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet包. 删除这个包并且添加相同版本的 [Volo.Abp.EntityFrameworkCore.Oracle.Devart](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) 包. - -## 替换模块依赖项 - -在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCoreOracleDevartModule)` (或者替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.Oracle.Devart;`). - -## UseOracle() - -Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: - -* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. -* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. - -In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block - -查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseOracle()`. 检查下列文件: - -* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. -* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. - -使用以下代码替换*YourProjectName*MigrationsDbContextFactory.cs中的 `CreateDbContext()` 方法: - -```csharp -var builder = new DbContextOptionsBuilder() - .UseSqlServer(configuration.GetConnectionString("Default")); -``` - -与这个 - -```csharp -var builder = (DbContextOptionsBuilder) - new DbContextOptionsBuilder().UseOracle - ( - configuration.GetConnectionString("Default") - ); -``` - -> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. - -## 更改连接字符串 - -Oracle连接字符串与SQL Server连接字符串不同. 所以检查你的解决方案中所有的 `appsettings.json` 文件,更改其中的连接字符串. 有关oracle连接字符串选项的详细内容请参见[connectionstrings.com](https://www.connectionstrings.com/oracle/). - -通常需要更改 `.DbMigrator` 和 `.Web` 项目里面的 `appsettings.json` ,但它取决于你的解决方案结构. - -## 重新生成迁移 - -启动模板使用[Entity Framework Core的Code First迁移](https://docs.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/). EF Core迁移取决于所选的DBMS提供程序. 因此更改DBMS提供程序会导致迁移失败. - -* 删除 `.EntityFrameworkCore.DbMigrations` 项目下的Migrations文件夹,并重新生成解决方案. -* 在包管理控制台中运行 `Add-Migration "Initial"`(在解决方案资源管理器选择 `.DbMigrator` (或 `.Web`) 做为启动项目并且选择 `.EntityFrameworkCore.DbMigrations` 做为默认项目). - -这将创建一个配置所有数据库对象(表)的数据库迁移. - -运行 `.DbMigrator` 项目创建数据库和初始种子数据. - -## 运行应用程序 - -它已准备就绪, 只需要运行该应用程序与享受编码. \ No newline at end of file +> 你可以选择一个你想要的包,如果你不知道它们之间的区别,请在网站上进行搜索. ABP框架仅提供集成,不提供第三库类库的支持. \ No newline at end of file diff --git a/docs/zh-Hans/Entity-Framework-Core-PostgreSQL.md b/docs/zh-Hans/Entity-Framework-Core-PostgreSQL.md index 350cff65b0..6e481bb230 100644 --- a/docs/zh-Hans/Entity-Framework-Core-PostgreSQL.md +++ b/docs/zh-Hans/Entity-Framework-Core-PostgreSQL.md @@ -8,14 +8,14 @@ ## 替换模块依赖项 -在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCorePostgreSqlModule)` (或者替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.PostgreSql;`). +在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCorePostgreSqlModule)` (并且替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.PostgreSql;`). ## UseNpgsql() 查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseNpgsql()`. 检查下列文件: * `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. -* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. +* `.EntityFrameworkCore.DbMigrations` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. > 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. diff --git a/docs/zh-Hans/Entity-Framework-Core-SQLite.md b/docs/zh-Hans/Entity-Framework-Core-SQLite.md index d75f8ba5b7..646e3082d5 100644 --- a/docs/zh-Hans/Entity-Framework-Core-SQLite.md +++ b/docs/zh-Hans/Entity-Framework-Core-SQLite.md @@ -8,14 +8,14 @@ ## 替换模块依赖项 -在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCoreSqliteModule)` (或者替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.Sqlite;`). +在 `.EntityFrameworkCore` 项目中找到 **YourProjectName*EntityFrameworkCoreModule** 类, 删除 `DependsOn` attribute 上的`typeof(AbpEntityFrameworkCoreSqlServerModule)`, 添加 `typeof(AbpEntityFrameworkCoreSqliteModule)` (并且替换 `using Volo.Abp.EntityFrameworkCore.SqlServer;` 为 `using Volo.Abp.EntityFrameworkCore.Sqlite;`). ## UseSqlite() 查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseSqlite()`. 检查下列文件: * `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. -* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. +* `.EntityFrameworkCore.DbMigrations` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. > 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md index e2472bdcee..9a71643a51 100644 --- a/docs/zh-Hans/Tutorials/Part-1.md +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -737,8 +737,8 @@ $(function () { 是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: - [Angular CLI](https://angular.io/cli) 用于创建模块,组件和服务. -- [NGXS](https://ngxs.gitbook.io/ngxs/) 用于管理状态库. - [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. +- [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) 用做 datatable 类库. - [Visual Studio Code](https://code.visualstudio.com/) 用做代码编辑器 (你可以选择自己喜欢的编辑器). #### 安装 NPM 包 @@ -761,26 +761,56 @@ yarn ng generate module book --routing true #### 路由 -打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的 `import` 和路由: +打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的路由: ```js -import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added this line to imports <== +const routes: Routes = [ +// ... +// added a new route to the routes array + { + path: 'books', + loadChildren: () => import('./book/book.module').then(m => m.BookModule) + } +] +``` -//...added book path with the below to the routes array -{ - path: 'book', - component: ApplicationLayoutComponent, - loadChildren: () => import('./book/book.module').then(m => m.BookModule), - data: { - routes: { - name: '::Menu:Book', - iconClass: 'fas fa-book' - } as ABP.Route - }, +* 我们添加了一个懒加载路由. 参阅 [嬾加載功能模块](https://angular.io/guide/lazy-loading-ngmodules#lazy-loading-feature-modules). + +打开位于 `src\app` 目录下的 `route.provider.ts` 文件,用以下内容替换它: + +```js +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { APP_INITIALIZER } from '@angular/core'; + +export const APP_ROUTE_PROVIDER = [ + { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, +]; + +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + //... + // added below element + { + path: '/books', + name: '::Menu:Books', + iconClass: 'fas fa-book', + order: 101, + layout: eLayoutType.application, + }, + ]); + }; } ``` -* `ApplicationLayoutComponent` 配置将应用程序布局设置为新页面, 我们添加了 `data` 对象. `name` 是菜单项的名称,`iconClass` 是菜单项的图标. +* 我们添加了一个新的路由元素在菜单上显示为 "Books" 的导航元素. + * `path` 路由的URL. + * `name` 菜单项的名称,可以使用本地化Key. + * `iconClass` 菜单项的图标. + * `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面. + * `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application`, `eLayoutType.account` 或 `eLayoutType.empty`. + +更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). #### Book 列表组件 @@ -848,35 +878,6 @@ yarn start ![Initial book list page](./images/bookstore-initial-book-list-page.png) -#### 创建 BookState - -运行以下命令创建名为 `BookState` 的新state: - -```bash -npx @ngxs/cli --name book --directory src/app/book -``` - -* 此命令在 `src/app/book/state` 文件夹下创建了 `book.state.ts` 和 `book.actions.ts` 文件. 参阅 [NGXS CLI文档](https://www.ngxs.io/plugins/cli)了解更多. - -将 `BookState` 导入到 `src/app` 文件夹中的 `app.module.ts` 中. 然后添加 `BookState` 到 `NgxsModule` 的 `forRoot` 静态方法,作为该方法的第一个参数的数组元素. - -```js -// ... -import { BookState } from './book/state/book.state'; //<== imported BookState ==> - -@NgModule({ - imports: [ - // other imports - - NgxsModule.forRoot([BookState]), //<== added BookState ==> - - //other imports - ], - // ... -}) -export class AppModule {} -``` - #### 生成代理 ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档](../CLI.md). @@ -893,109 +894,41 @@ abp generate-proxy --module app ![Generated files](./images/generated-proxies.png) -#### GetBook 动作 - -动作可以被认为是一个命令,它应该触发某些事情发生,或者是已经发生的事情的结果事件.[See NGXS Actions文档](https://www.ngxs.io/concepts/actions). - -打开 `app/book/state` 目录下的 `book.actions.ts` 文件用以下内容替换它: - -```js -export class GetBook { - static readonly type = '[Book] Get'; -} -``` - -#### 实现 BookState - -打开 `app/book/state` 目录下的 `book.state.ts` 文件用以下内容替换它: - -```js -import { PagedResultDto } from '@abp/ng.core'; -import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks } from './book.actions'; -import { BookService } from '../services'; -import { tap } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; -import { BookDto } from '../models'; - -export class BookStateModel { - public book: PagedResultDto; -} - -@State({ - name: 'BookState', - defaults: { book: {} } as BookStateModel, -}) -@Injectable() -export class BookState { - @Selector() - static getBooks(state: BookStateModel) { - return state.book.items || []; - } - - constructor(private bookService: BookService) {} - - @Action(GetBooks) - get(ctx: StateContext) { - return this.bookService.getListByInput().pipe( - tap((booksResponse) => { - ctx.patchState({ - book: booksResponse, - }); - }) - ); - } -} -``` - -* 我们添加了book属性到BookStateModel模态框. -* 我们添加了 `GetBook` 动作. 它通过 ABP CLI生成的 `BookService` 检索图书数据. -* `NGXS` 需要在不订阅get函数的情况下返回被观察对象. - #### BookListComponent 打开 `app\book\book-list` 目录下的 `book-list.component.ts` 用以下内容替换它: ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks } from '../state/book.actions'; -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; @Component({ selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], + providers: [ListService], }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; - loading = false; - - constructor(private store: Store) {} + constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } } ``` -* 我们添加了 `get` 函数获取book更新store. -* 有关 `NGXS` 特性的更多信息请参见NGXS文档中的[Dispatching actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions)和[Select](https://ngxs.gitbook.io/ngxs/concepts/select). +* 我们注入了生成的 `BookService`. +* 我们实现了 [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service),它是一个公用服务,提供了简单的分页,排序和搜索. 打开 `app\book\book-list` 目录下的 `book-list.component.html` 用以下内容替换它: @@ -1005,38 +938,31 @@ export class BookListComponent implements OnInit {
- {%{{{ "::Menu:Book" | abpLocalization }}}%} + {%{{{ '::Menu:Books' | abpLocalization }}}%}
- - - - - {%{{{ "::Name" | abpLocalization }}}%} - {%{{{ "::Type" | abpLocalization }}}%} - {%{{{ "::PublishDate" | abpLocalization }}}%} - {%{{{ "::Price" | abpLocalization }}}%} - - - - - {%{{{ data.name }}}%} - {%{{{ bookType[data.type] }}}%} - {%{{{ data.publishDate | date }}}%} - {%{{{ data.price }}}%} - - + + + + + {%{{{ booksType[row.type] }}}%} + + + + + {%{{{ row.publishDate | date }}}%} + + + + + {%{{{ row.price | currency }}}%} + + +
``` diff --git a/docs/zh-Hans/Tutorials/Part-2.md b/docs/zh-Hans/Tutorials/Part-2.md index 74a52802b0..5ddc8c2a66 100644 --- a/docs/zh-Hans/Tutorials/Part-2.md +++ b/docs/zh-Hans/Tutorials/Part-2.md @@ -454,78 +454,48 @@ $(function () { 下面的章节中,你将学习到如何创建一个新的模态对话框来新增Book实体. -#### 状态定义 +#### 添加 modal 到 BookListComponent -在 `app\book\state` 文件夹下打开 `book.action.ts` 文件,使用以下内容替换它: -```js -import { CreateUpdateBookDto } from '../models'; //<== added this line ==> - -export class GetBooks { - static readonly type = '[Book] Get'; -} +Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: -// added CreateUpdateBook class -export class CreateUpdateBook { - static readonly type = '[Book] Create Update Book'; - constructor(public payload: CreateUpdateBookDto) { } -} -``` +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto, BookType } from '../models'; +import { BookService } from '../services'; -* 我们导入了 `CreateUpdateBookDto` 模型并且创建了 `CreateUpdateBook` 动作. +@Component({ + selector: 'app-book-list', + templateUrl: './book-list.component.html', + styleUrls: ['./book-list.component.scss'], + providers: [ListService], +}) +export class BookListComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; -打开 `app\book\state` 文件夹下的 `book.state.ts` 文件,使用以下内容替换它: + booksType = BookType; -```js -import { PagedResultDto } from '@abp/ng.core'; -import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks, CreateUpdateBook } from './book.actions'; // <== added CreateUpdateBook==> -import { BookService } from '../services'; -import { tap } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; -import { BookDto } from '../models'; + isModalOpen = false; // <== added this line ==> -export class BookStateModel { - public book: PagedResultDto; -} + constructor(public readonly list: ListService, private bookService: BookService) {} -@State({ - name: 'BookState', - defaults: { book: {} } as BookStateModel, -}) -@Injectable() -export class BookState { - @Selector() - static getBooks(state: BookStateModel) { - return state.book.items || []; - } + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - constructor(private bookService: BookService) {} - - @Action(GetBooks) - get(ctx: StateContext) { - return this.bookService.getListByInput().pipe( - tap((bookResponse) => { - ctx.patchState({ - book: bookResponse, - }); - }) - ); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } - // added CreateUpdateBook action listener - @Action(CreateUpdateBook) - save(ctx: StateContext, action: CreateUpdateBook) { - return this.bookService.createByInput(action.payload); + // added createBook method + createBook() { + this.isModalOpen = true; } } ``` -* 我们导入了 `CreateUpdateBook` 动作并且定义了 `save` 方法监听 `CreateUpdateBook` 动作去创建图书. - -当 `SaveBook` 动作被分派时,save方法被执行. 它调用 `BookService` 的 `createByInput` 方法. - -#### 添加模态到 BookListComponent +* 我们定义了一个名为 `isModalOpen` 的变量和 `createBook` 方法. 打开 `app\book\book-list` 文件夹内的 `book-list.component.html` 文件,使用以下内容替换它: @@ -534,19 +504,12 @@ export class BookState {
-
- {%{{{ '::Menu:Books' | abpLocalization }}}%} -
+
{%{{{ '::Menu:Books' | abpLocalization }}}%}
- +
- @@ -555,47 +518,40 @@ export class BookState {
- - - - - {%{{{ "::Name" | abpLocalization }}}%} - {%{{{ "::Type" | abpLocalization }}}%} - {%{{{ "::PublishDate" | abpLocalization }}}%} - {%{{{ "::Price" | abpLocalization }}}%} - - - - - {%{{{ data.name }}}%} - {%{{{ bookType[data.type] }}}%} - {%{{{ data.publishDate | date }}}%} - {%{{{ data.price }}}%} - - + + + + + {%{{{ booksType[row.type] }}}%} + + + + + {%{{{ row.publishDate | date }}}%} + + + + + {%{{{ row.price | currency }}}%} + + +
- -

{%{{{ '::NewBook' | abpLocalization }}}%}

-
+ +

{%{{{ '::NewBook' | abpLocalization }}}%}

+
- + - - - + + +
``` @@ -603,55 +559,6 @@ export class BookState { * `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些附加的好处. * 我们添加了 `New book` 按钮到 `AbpContentToolbar`. -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: - -```js -import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../models'; -import { GetBooks } from '../state/book.actions'; -import { BookState } from '../state/book.state'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], -}) -export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; - - booksType = BookType; - - loading = false; - - isModalOpen = false; // <== added this line ==> - - constructor(private store: Store) {} - - ngOnInit() { - this.get(); - } - - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); - } - - // added createBook method - createBook() { - this.isModalOpen = true; - } -} -``` - -* 我们添加了 `isModalOpen = false` 和 `createBook` 方法. - 你可以打开浏览器,点击**New book**按钮看到模态框. ![Empty modal for new book](./images/bookstore-empty-new-book-modal.png) @@ -660,51 +567,46 @@ export class BookListComponent implements OnInit { [响应式表单](https://angular.io/guide/reactive-forms) 提供一种模型驱动的方法来处理其值随时间变化的表单输入. -打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: +打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks } from '../state/book.actions'; -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==> @Component({ selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], + providers: [ListService], }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; - loading = false; - isModalOpen = false; form: FormGroup; // <== added this line ==> - constructor(private store: Store, private fb: FormBuilder) {} // <== added FormBuilder ==> + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder // <== injected FormBuilder ==> + ) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } createBook() { - this.buildForm(); //<== added this line ==> + this.buildForm(); // <== added this line ==> this.isModalOpen = true; } @@ -729,7 +631,7 @@ export class BookListComponent implements OnInit { #### 创建表单的DOM元素 -打开 `app\app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 ` `: +打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 ` `: ```html @@ -798,16 +700,13 @@ export class BooksModule { } * 我们导入了 `NgbDatepickerModule` 来使用日期选择器. -打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: +打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks } from '../state/book.actions'; -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==> @@ -815,37 +714,34 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], - providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added this line ==> + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added a provide ==> }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; - //added bookTypeArr array + // <== added bookTypeArr array ==> bookTypeArr = Object.keys(BookType).filter( (bookType) => typeof this.booksType[bookType] === 'number' ); - loading = false; - isModalOpen = false; form: FormGroup; - constructor(private store: Store, private fb: FormBuilder) {} + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } createBook() { @@ -882,16 +778,13 @@ export class BookListComponent implements OnInit { #### 保存图书 -打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: +打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks, CreateUpdateBook } from '../state/book.actions'; // <== added CreateUpdateBook ==> -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; @@ -899,11 +792,10 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], - providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - books$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; booksType = BookType; @@ -911,24 +803,22 @@ export class BookListComponent implements OnInit { (bookType) => typeof this.booksType[bookType] === 'number' ); - loading = false; - isModalOpen = false; form: FormGroup; - constructor(private store: Store, private fb: FormBuilder) {} + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } createBook() { @@ -951,19 +841,18 @@ export class BookListComponent implements OnInit { return; } - this.store.dispatch(new CreateUpdateBook(this.form.value)).subscribe(() => { + this.bookService.createByInput(this.form.value).subscribe(() => { this.isModalOpen = false; this.form.reset(); - this.get(); + this.list.get(); }); } } ``` -* 我们导入了 `CreateUpdateBook`. * 我们添加了 `save` 方法. -打开 `app\app\book\book-list` 文件夹下的 `app\app\book\book-list`文件, 添加 `abp-button` 保存图书. +打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件, 找到 `` 元素,使用下面元素替换它: ```html @@ -994,90 +883,49 @@ export class BookListComponent implements OnInit { ### 更新图书 -#### CreateUpdateBook 动作 - -打开 `app\book\state` 文件夹下的 `book.actions.ts` 文件,使用以下内容替换它: - -```js -import { CreateUpdateBookDto } from '../models'; - -export class GetBooks { - static readonly type = '[Book] Get'; -} - -export class CreateUpdateBook { - static readonly type = '[Book] Create Update Book'; - constructor(public payload: CreateUpdateBookDto, public id?: string) {} // <== added id parameter ==> -} -``` - -* 我们在 `CreateUpdateBook` 动作的构造函数添加了 `id` 参数. - -打开 `app\book\state` 文件夹下的 `book.state.ts` 文件,使用以下内容替换 `save` 方法: - -```js -@Action(CreateUpdateBook) -save(ctx: StateContext, action: CreateUpdateBook) { - if (action.id) { - return this.bookService.updateByIdAndInput(action.payload, action.id); - } else { - return this.bookService.createByInput(action.payload); - } -} -``` - -#### BookListComponent - -打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,在构造函数注入 `BookService` 服务,并添加 名为 `selectedBook` 的变量. +打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件并且添加名为 `selectedBook` 的变量. ```js +import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; import { BookDto, BookType } from '../models'; -import { GetBooks, CreateUpdateBook } from '../state/book.actions'; -import { BookState } from '../state/book.state'; +import { BookService } from '../services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; -import { BookService } from '../services'; // <== imported BookService ==> @Component({ selector: 'app-book-list', templateUrl: './book-list.component.html', styleUrls: ['./book-list.component.scss'], - providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], }) export class BookListComponent implements OnInit { - @Select(BookState.getBooks) - book$: Observable; + book = { items: [], totalCount: 0 } as PagedResultDto; - bookType = BookType; + booksType = BookType; bookTypeArr = Object.keys(BookType).filter( - (bookType) => typeof this.bookType[bookType] === 'number' + (bookType) => typeof this.booksType[bookType] === 'number' ); - loading = false; - isModalOpen = false; form: FormGroup; selectedBook = {} as BookDto; // <== declared selectedBook ==> - constructor(private store: Store, private fb: FormBuilder, private bookService: BookService) {} //<== injected BookService ==> + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} ngOnInit() { - this.get(); - } + const bookStreamCreator = (query) => this.bookService.getListByInput(query); - get() { - this.loading = true; - this.store - .dispatch(new GetBooks()) - .pipe(finalize(() => (this.loading = false))) - .subscribe(() => {}); + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); } // <== this method is replaced ==> @@ -1109,58 +957,46 @@ export class BookListComponent implements OnInit { }); } + // <== this method is replaced ==> save() { if (this.form.invalid) { return; } - //<== added this.selectedBook.id ==> - this.store - .dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id)) - .subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.get(); - }); + // <== added request ==> + const request = this.selectedBook.id + ? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id) + : this.bookService.createByInput(this.form.value); + + request.subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); } } ``` -* 我们导入了 `BookService`. * 我们声明了类型为 `BookDto` 的 `selectedBook` 变量. -* 我们在构造函数注入了 `BookService`, 它用于检索正在编辑的图书数据. * 我们添加了 `editBook` 方法, 根据给定图书 `Id` 设置 `selectedBook` 对象. * 我们替换了 `buildForm` 方法使用 `selectedBook` 数据创建表单. * 我们替换了 `createBook` 方法,设置 `selectedBook` 为空对象. -* 我们在 `CreateUpdateBook` 构造函数添加了 `selectedBook.id`. +* 我们替换了 `save` 方法. #### 添加 "Actions" 下拉框到表格 -打开 `app\app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `
` 标签: +打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `
` 标签: ```html
- - - - - {%{{{ "::Actions" | abpLocalization }}}%} - {%{{{ "::Name" | abpLocalization }}}%} - {%{{{ "::Type" | abpLocalization }}}%} - {%{{{ "::PublishDate" | abpLocalization }}}%} - {%{{{ "::Price" | abpLocalization }}}%} - - - - - + + + +
-
- - {%{{{ data.name }}}%} - {%{{{ bookType[data.type] }}}%} - {%{{{ data.publishDate | date }}}%} - {%{{{ data.price }}}%} - -
+
+ + + + + {%{{{ booksType[row.type] }}}%} + + + + + {%{{{ row.publishDate | date }}}%} + + + + + {%{{{ row.price | currency }}}%} + + +
``` - 我们添加了 "Actions" 栏的 `th`. +- 我们为 "Actions" 栏添加了 `ngx-datatable-column`. - 我们添加了带有 `ngbDropdownToggle` 的 `button`,在点击按钮时打开操作. - 我们习惯于将[NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples)用于操作的下拉菜单. @@ -1194,7 +1043,7 @@ UI最终看起来像这样: ![Action buttons](./images/bookstore-actions-buttons.png) -打开 `app\app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `` 标签: +打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `` 标签: ```html @@ -1206,80 +1055,9 @@ UI最终看起来像这样: ### 删除图书 -#### DeleteBook 动作 - -打开 `app\book\state` 文件夹下的 `book.actions.ts` 文件添加名为 `DeleteBook` 的动作. - -```js -export class DeleteBook { - static readonly type = '[Book] Delete'; - constructor(public id: string) {} -} -``` - -打开 `app\book\state` 文件夹下的 `book.state.ts` 文件,使用以下内容替换它: - -```js -import { PagedResultDto } from '@abp/ng.core'; -import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks, CreateUpdateBook, DeleteBook } from './book.actions'; // <== added DeleteBook==> -import { BookService } from '../services'; -import { tap } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; -import { BookDto } from '../models'; - -export class BookStateModel { - public book: PagedResultDto; -} - -@State({ - name: 'BookState', - defaults: { book: {} } as BookStateModel, -}) -@Injectable() -export class BookState { - @Selector() - static getBooks(state: BookStateModel) { - return state.book.items || []; - } - - constructor(private bookService: BookService) {} - - @Action(GetBooks) - get(ctx: StateContext) { - return this.bookService.getListByInput().pipe( - tap((booksResponse) => { - ctx.patchState({ - book: booksResponse, - }); - }) - ); - } - - @Action(CreateUpdateBook) - save(ctx: StateContext, action: CreateUpdateBook) { - if (action.id) { - return this.bookService.updateByIdAndInput(action.payload, action.id); - } else { - return this.bookService.createByInput(action.payload); - } - } - - // <== added DeleteBook action listener ==> - @Action(DeleteBook) - delete(ctx: StateContext, action: DeleteBook) { - return this.bookService.deleteById(action.id); - } -} -``` - -- 我们导入了 `DeleteBook` . - -- 我们在文件末尾添加了 `DeleteBook` 动作监听器. - #### 删除确认弹层 -打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,注入 `ConfirmationService`. +打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,注入 `ConfirmationService`. 替换构造函数: @@ -1288,50 +1066,46 @@ import { ConfirmationService } from '@abp/ng.theme.shared'; //... constructor( - private store: Store, - private fb: FormBuilder, - private bookService: BookService, - private confirmation: ConfirmationService // <== added this line ==> -) { } + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder, + private confirmation: ConfirmationService // <== added this line ==> +) {} ``` -* 我们导入了 `ConfirmationService`. -* 我们在构造函数注入了 `ConfirmationService` . +* We imported `ConfirmationService`. +* We injected `ConfirmationService` to the constructor. -参阅[确认弹层文档](https://docs.abp.io/en/abp/latest/UI/Angular/Confirmation-Service)了解更多 +See the [Confirmation Popup documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Confirmation-Service) -在 `book-list.component.ts` 中添加删除方法: +In the `book-list.component.ts` add a delete method: ```js -import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/book.actions' ;// <== imported DeleteBook ==> - -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation ==> +import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation namespace ==> //... delete(id: string) { - this.confirmation - .warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure') - .subscribe(status => { - if (status === Confirmation.Status.confirm) { - this.store.dispatch(new DeleteBook(id)).subscribe(() => this.get()); - } - }); + this.confirmation.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure').subscribe((status) => { + if (status === Confirmation.Status.confirm) { + this.bookService.deleteById(id).subscribe(() => this.list.get()); + } + }); } ``` -`delete` 方法会显示一个确认弹层并订阅用户响应. 只在用户点击 `Yes` 按钮时分派动作. 确认弹层看起来如下: +`delete` 方法会显示一个确认弹层并订阅用户响应. 只在用户点击 `Yes` 按钮时调用 `BookService` 的 `deleteById` 方法. 确认弹层看起来如下: ![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png) #### 添加删除按钮 -打开 `app\app\book\book-list` 文件夹下的 `app\app\book\book-list` 文件,修改 `ngbDropdownMenu` 添加删除按钮: +打开 `app\book\book-list` 文件夹下的 `app\book\book-list` 文件,修改 `ngbDropdownMenu` 添加删除按钮: ```html
-
diff --git a/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png b/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png index e8243fedc7..f5d8f0870b 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png and b/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png index ffa8dcd7e2..d921f146fc 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png and b/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png index d402895c9b..ecdb87c737 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png and b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png b/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png index a80b180f1c..b17e70638d 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png and b/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png b/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png index 2a02802bb9..f4e969ae22 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png and b/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png differ diff --git a/docs/zh-Hans/UI/Angular/List-Service.md b/docs/zh-Hans/UI/Angular/List-Service.md index 6d5bc6f1ef..39275fdf10 100644 --- a/docs/zh-Hans/UI/Angular/List-Service.md +++ b/docs/zh-Hans/UI/Angular/List-Service.md @@ -53,57 +53,38 @@ class BookComponent { > 注意 `list` 是 `public` 并且 `readonly`. 因为我们将直接在组件的模板中使用 `ListService` 属性. 可以视为反模式,但是实现起来要快得多. 你可以改为使用公共组件属性. -将 `ListService` 属性放入模板中,如下所示: +像这样绑定 `ListService` 到 ngx-datatable: ```html - - - - - - - {%{{{ '::Name' | abpLocalization }}}%} - - - - - - - - {%{{{ data.name }}}%} - - + + ``` ## 与Observables一起使用 你可以将Observables与Angular的[AsyncPipe](https://angular.io/guide/observables-in-angular#async-pipe)结合使用: -```ts +```js book$ = this.list.hookToQuery(query => this.bookService.getListByInput(query)); ``` ```html - - + + ``` @@ -111,7 +92,7 @@ class BookComponent { ...or... -```ts +```js @Select(BookState.getBooks) books$: Observable; @@ -126,13 +107,18 @@ class BookComponent { ```html - - + + ``` +> 我们不建议将NGXS存储用于CRUD页面,除非你的应用程序需要在组件之间共享列表信息或稍后在另一页面中使用它. + ## 如何在创建/更新/删除时刷新表 `ListService` 公开了一个 `get` 方法来触发当前查询的请求. 因此基本上每当创建,更新或删除操作解析时,你可以调用 `this.list.get();` 它会调用钩子流创建者. @@ -161,3 +147,26 @@ this.bookService.createByInput(form.value) ``` + +## ABP v3.0的重大更改 + +我们必须修改 `ListService` 使其与 `ngx-datatable` 一起使用. 之前 `page` 属性的最小值为 `1`, 你可以像这样使用它: + +```html + + +``` + +从v3.0开始, 对于`ngx-datatable`, 初始页面的 `page`属性必须设置为 `0`. 因此如果你以前在表上使用过 `ListService` 并打算保留 `abp-table`,则需要进行以下更改: + +```html + + +``` + +**重要提示:** `abp-table` 没有被删除,但是会被弃用,并在将来的版本中移除,请考虑切换到 ngx-datatable. \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md b/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md new file mode 100644 index 0000000000..36afffa54f --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md @@ -0,0 +1,363 @@ +# Angular UI v2.9 迁移到 v3.0 指南 + +## 在v3.0改变了什么? + +### Angular 10 + +新的ABP Angular UI基于Angular 10和TypeScript 3.9,我们已经放弃了对Angular 8的支持. 不过ABP模块将继续与Angular 9兼容使用. 因此如果你的项目是Angular 9,则无需更新为 Angular10. 更新通常很容易. + +#### 如何迁移? + +在你的根文件夹中打开一个终端,然后运行以下命令: + +```sh +yarn ng update @angular/cli @angular/core --force +``` + +这会做如下修改: + +- 更新你的package.json并安装新的软件包 +- 修改tsconfig.json文件创建一个"Solution Style"配置 +- 重命名 `browserlist` 为 `.browserlistrc` + +另一方面,如果你单独使用 `yarn ng update` 命令检查首先要更新哪些包会更好. Angular会给你一个要更新的包列表. + +![Table of packages to update](./images/table-of-packages-to-update.png) + +当Angular报告上面的包后,运行命令: + +```sh +yarn ng update @angular/cli @angular/core ng-zorro-antd --force +``` + +> 如果Angular提示你的仓库有中未提交的更改,可以提交/存储它,也可以在命令中添加 `--allow-dirty` 参数. + +### 配置模块 + +在ABP v2.x中,每个延迟加载的模块都有一个可通过单独的程序包使用的配置模块,模块配置如下: + +```js +import { AccountConfigModule } from '@abp/ng.account.config'; + +@NgModule({ + imports: [ + // other imports + AccountConfigModule.forRoot({ redirectUrl: '/' }), + ], + // providers, declarations, and bootstrap +}) +export class AppModule {} +``` + +...在app-routing.module.ts... + +```js +const routes: Routes = [ + // other route configuration + { + path: 'account', + loadChildren: () => import( + './lazy-libs/account-wrapper.module' + ).then(m => m.AccountWrapperModule), + }, +]; +``` + +虽然有效,但有一些缺点: + +- 每个模块都有两个独立的程序包,但实际上这些程序包是相互依赖的. +- 配置延迟加载的模块需要包装模块. +- ABP Commercial具有可扩展性系统,在根模块上配置可扩展模块会增加 bundle 的大小. + +在ABP v3.0中,我们为每个配置模块引入了辅助入口点,并且提供了一种在没有包装的情况下配置延迟加载的模块的新方法. 现在模块配置如下所示: + +```js +import { AccountConfigModule } from '@abp/ng.account/config'; + +@NgModule({ + imports: [ + // other imports + AccountConfigModule.forRoot(), + ], + // providers, declarations, and bootstrap +}) +export class AppModule {} +``` + +... 在app-routing.module.ts... + +```js +const routes: Routes = [ + // other route configuration + { + path: 'account', + loadChildren: () => import('@abp/ng.account') + .then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), + }, +]; +``` + +这项更改帮助我们减少了捆绑包的大小并大大缩短了构建时间. 我们相信你会注意到你的应用程序有所不同. + +#### 一个更好的例子 + +AppModule: + +```js +import { AccountConfigModule } from '@abp/ng.account/config'; +import { CoreModule } from '@abp/ng.core'; +import { IdentityConfigModule } from '@abp/ng.identity/config'; +import { SettingManagementConfigModule } from '@abp/ng.setting-management/config'; +import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config'; +import { ThemeBasicModule } from '@abp/ng.theme.basic'; +import { ThemeSharedModule } from '@abp/ng.theme.shared'; +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NgxsModule } from '@ngxs/store'; +import { environment } from '../environments/environment'; +import { AppRoutingModule } from './app-routing.module'; + +@NgModule({ + imports: [ + BrowserModule, + BrowserAnimationsModule, + AppRoutingModule, + CoreModule.forRoot({ + environment, + sendNullsAsQueryParam: false, + skipGetAppConfiguration: false, + }), + ThemeSharedModule.forRoot(), + AccountConfigModule.forRoot(), + IdentityConfigModule.forRoot(), + TenantManagementConfigModule.forRoot(), + SettingManagementConfigModule.forRoot(), + ThemeBasicModule.forRoot(), + NgxsModule.forRoot(), + ], + // providers, declarations, and bootstrap +}) +export class AppModule {} +``` + +AppRoutingModule: + +```js +import { DynamicLayoutComponent } from '@abp/ng.core'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: '', + component: DynamicLayoutComponent, + children: [ + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./home/home.module') + .then(m => m.HomeModule), + }, + { + path: 'account', + loadChildren: () => import('@abp/ng.account') + .then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), + }, + { + path: 'identity', + loadChildren: () => import('@abp/ng.identity') + .then(m => m.IdentityModule.forLazy()), + }, + { + path: 'tenant-management', + loadChildren: () => import('@abp/ng.tenant-management') + .then(m => m.TenantManagementModule.forLazy()), + }, + { + path: 'setting-management', + loadChildren: () => import('@abp/ng.setting-management') + .then(m => m.SettingManagementModule.forLazy()), + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} +``` + +> 你可能已经注意到我们在top级别路由组件上使用了 `DynamicLayoutComponent`. 我们这样做是为了避免不必要的渲染和闪烁. 这不是强制的,但是我们建议在你的应用程序路由中做同样的事情. + +#### 如何迁移? + +- 使用 `yarn remove` 删除你的项目的配置包. +- 从辅助入口点(例如`@abp/ng.identity/config`)导入配置模块. +- 调用所有新配置模块的静态 `forRoot`方法,即使配置没有被传递. +- 调用 `ThemeBasicModule` 的静态 `forRoot` 方法(或商业上的 `ThemeLeptonModule`),并从导入中删除 `SharedModule`(除非已在其中添加了根模块所需的任何内容). +- 在app路由模块中直接导入延迟ABP模块 (如 `() => import('@abp/ng.identity').then(...)`). +- 在所有延迟模块 `then` 中调用的静态 `forLazy` 方法,即使配置没有被传递. +- [可选]使用 `DynamicLayoutComponent` 添加空的父路由,获得更好的性能和UX. + +### RoutesService + +在ABP v2.x中,通过以下两种方式之一将路由添加到菜单: + +- [通过 `AppRoutingModule` 的 `routes` 属性](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#via-routes-property-in-approutingmodule) +- [通过 ConfigState](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#via-configstate) + +从v3.0开始,我们更改了添加和修改路由的方式. 我们不再将路由存储在 `ConfigState`中(破坏性更改). 而是有一个名为 `RoutesService` 的新服务,该服务用于添加,修补或删除菜单项. 详情请查看[文档](Modifying-the-Menu.md). + +#### 如何迁移? + +- 检查你是否曾经使用 `ConfigState` 或 `ConfigStateService` 添加路由. 请用 `RoutesService` 的 `add` 方法替换它们. +- 检查你是否曾经修补的路由. 将其替换为 `RoutesService` 的 `patch` 方法. +- 仔细检查你是否使用绝对路径,并在 `add` 或 `patch` 方法调用中为子菜单项提供 `parentName` 而不是 `children` 属性. + +### NavItemsService + +在ABP v2.x中,[通过LayoutStateService](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#how-to-add-an-element-to-right-part-of-the-menu)添加导航元素. + +从v3.0开始,我们改变了添加和修改导航项的方式,以前的方法不再可用(破坏性更改). 详情请查看[文档](Modifying-the-Menu.md). + +#### 如何迁移? + +- 用 `NavItemsService` 的 `addItems` 方法替换所有 `dispatchAddNavigationElement` 调用. + +### ngx-datatable + +在v3之前,我们一直使用自定义组件 `abp-table` 作为默认表. 但是数据表是复杂的组件,要实现功能齐全的数据表需要大量的精力,我们计划将其引入其他功能. + +从ABP v3开始,我们已切换到经过严格测试,执行良好的数据表格:[ngx-datatable](https://github.com/swimlane/ngx-datatable). 所有的ABP模块都已经实现了ngx-datatable. `ThemeSharedModule` 已经导出了 `NgxDatatableModule`. 因此如果你在终端运行 `yarn add @swimlane/ngx-datatable` 来安装这个包,它将在你的应用的所有模块中可用. + +为了正确设置样式,你需要在angular.json文件的样式部分中添加以下内容: + +```json + { + "input": "node_modules/@swimlane/ngx-datatable/index.css", + "inject": true, + "bundleName": "ngx-datatable-index" + }, + { + "input": "node_modules/@swimlane/ngx-datatable/assets/icons.css", + "inject": true, + "bundleName": "ngx-datatable-icons" + }, + { + "input": "node_modules/@swimlane/ngx-datatable/themes/material.css", + "inject": true, + "bundleName": "ngx-datatable-material" + } +``` + +由于尚未删除 `abp-table`, 因此以前由ABP v2.x构建的模块不会突然丢失所有. 但是它们的外观与内置ABP v3模块有所不同, 因此你可能希望将这些模块中的表转换为ngx-datatable. 为了减少将abp-table转换为ngx-datatable所需的工作量,我们修改了 `ListService` 以使其与 `ngx-datatable` 一起很好地工作,并引入了两个新指令: `NgxDatatableListDirective` 和 `NgxDatatableDefaultDirective`. + +这些指令的用法很简单: + +```js +@Component({ + providers: [ListService], +}) +export class SomeComponent { + data$ = this.list.hookToQuery( + query => this.dataService.get(query) + ); + + constructor( + public readonly list: ListService, + public readonly dataService: SomeDataService, + ) {} +} +``` + +...在组件模板... + +```html + + + +``` + +通过 `NgxDatatableListDirective` 绑定注入的 `ListService` 实例后,你不再需要担心分页或排序. 同样 `NgxDatatableDefaultDirective` 去除了几个属性绑定,以使ngx-datatable适合我们的样式. + +#### 一个更好的例子 + +```html + + + + + + + + + + + + + + +
{%{{{ row.someOtherProp }}}%}
+
+
+
+``` + +#### 如何迁移? + +- 安装 `@swimlane/ngx-datatable` 包. +- 添加ngx-datatable样式到angular.json文件. +- 如果可以的话,根据上面的例子更新你的模. +- 如果你稍后需要这样做,并且打算保留abp-table一段时间,请确保根据此处描述的[破坏性更改](List-Service.md)更新分页. + +**重要说明:**abp-table没有被删除,但已被弃用并在以后的版本中删除. 请考虑切换到ngx-datatable。 + +### 过时的接口 + +某些接口早已被标记为已弃用,现在已将其删除. + +#### 如何迁移? + +请检查你是否仍在使用[Issue中列出的任何内容](https://github.com/abpframework/abp/issues/4281). + +## 下一步是什么? + +* [服务代理](Service-Proxies.md) \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md b/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md index ec3297ea78..1f4e626bd3 100644 --- a/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md +++ b/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md @@ -1,6 +1,5 @@ # 修改菜单 - 菜单在 @abp/ng.theme.basic包 `ApplicationLayoutComponent` 内部. 有几种修改菜单的方法,本文档介绍了这些方法. 如果你想完全替换菜单,请参考[组件替换文档]了解如何替换布局. @@ -24,7 +23,97 @@ export const environment = { ## 如何添加导航元素 -### 通过 AppRoutingModule 中的 `routes` 属性 +### 通过 `RoutesService` + +你可以通过调用 `RoutesService` 的 `add` 方法添加路由到菜单,它是一个单例的服务,在root中提供,你可以立即注入使用它. + +```js +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { Component } from '@angular/core'; + +@Component(/* component metadata */) +export class AppComponent { + constructor(routes: RoutesService) { + routes.add([ + { + path: '/your-path', + name: 'Your navigation', + order: 101, + iconClass: 'fas fa-question-circle', + requiredPolicy: 'permission key here', + layout: eLayoutType.application, + }, + { + path: '/your-path/child', + name: 'Your child navigation', + parentName: 'Your navigation', + order: 1, + requiredPolicy: 'permission key here', + }, + ]); + } +} +``` + +另一种方法是使用路由提供程序. 首先创建一个提供程序: + +```js +// route.provider.ts +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { APP_INITIALIZER } from '@angular/core'; + +export const APP_ROUTE_PROVIDER = [ + { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, +]; + +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + path: '/your-path', + name: 'Your navigation', + requiredPolicy: 'permission key here', + order: 101, + iconClass: 'fas fa-question-circle', + layout: eLayoutType.application, + }, + { + path: '/your-path/child', + name: 'Your child navigation', + parentName: 'Your navigation', + requiredPolicy: 'permission key here', + order: 1, + }, + ]); + }; +} +``` + +...然后在app.module.ts ... + +```js +import { NgModule } from '@angular/core'; +import { APP_ROUTE_PROVIDER } from './route.provider'; + +@NgModule({ + providers: [APP_ROUTE_PROVIDER], + // imports, declarations, and bootstrap +}) +export class AppModule {} +``` + +下面是每个属性的工作原理: + +- `path` 是导航元素的绝对路径. +- `name` 是导航元素的label. 可以使用本地化Key和本地化对象. +- `parentName` 是菜单中父路由的 `name` 的引用,用于创建多级菜单项. +- `requiredPolicy` 是用于访问该页面的权限Key. 参阅[权限管理文档](Permission-Management.md). +- `order` 是导航元素的排序. `Administration` 的顺序是 `100`. 在排序top级别菜单项时请记得这一点. +- `iconClass` 是 `i` 标签的class, 它放在导航label的左边. +- `layout` 定义路由使用哪个布局加载. (默认: `eLayoutType.empty`). +- `invisible` 使该项在菜单中不可见. (默认: `false`). + +### 通过 `AppRoutingModule` 的 `routes` 属性 你可以通过在 `app-routing.module` 中将路由作为子属性添加到路由配置的 `data` 属性来定义路由. `@abp/ng.core` 包组织路由并将其存储在 `ConfigState` 中.`ApplicationLayoutComponent` 从存储中获取路由显示在菜单上. @@ -36,7 +125,7 @@ export const environment = { data: { routes: { name: 'Your navigation', - order: 3, + order: 101, iconClass: 'fas fa-question-circle', requiredPolicy: 'permission key here', children: [ @@ -47,100 +136,77 @@ export const environment = { requiredPolicy: 'permission key here', }, ], - } as ABP.Route, // can be imported from @abp/ng.core - } + }, + }, } ``` -- `name` 是导航元素的标签,可以传递本地化密钥或本地化对象. -- `order` 排序导航元素. -- `iconClass` 是 `i` 标签的类,在导航标签的左侧. -- `requiredPolicy` 是访问页面所需的权限key. 参阅 [权限管理文档](./Permission-Management.md) -- `children` is an array and is used for declaring child navigation elements. The child navigation element will be placed as a child route which will be available at `'/your-path/child'` based on the given `path` property. -- `children` 是一个数组,用于声明子菜单,它基于给定的 `path` 属性,路径是在`/your-path/child`. - -添加了上面描述的route属性后,导航菜单如下图所示: - -![navigation-menu-via-app-routing](./images/navigation-menu-via-app-routing.png) - -## 通过 ConfigState - -`ConfigStateService` 的 `dispatchAddRoute` 方法可以向菜单添加一个新的导航元素. - -```js -// this.config is instance of ConfigStateService - -const newRoute: ABP.Route = { - name: 'My New Page', - iconClass: 'fa fa-dashboard', - path: 'page', - invisible: false, - order: 2, - requiredPolicy: 'MyProjectName.MyNewPage', -} as Omit; - -this.config.dispatchAddRoute(newRoute); -// returns a state stream which emits after dispatch action is complete -``` - -`newRoute` 放在根级别,没有任何父路由,url将为`/path`. - -如果你想 **添加子路由, 你可以这样做:** +或者你可以这样做: ```js -// this.config is instance of ConfigStateService -// eIdentityRouteNames enum can be imported from @abp/ng.identity - -const newRoute: ABP.Route = { - parentName: eIdentityRouteNames.IdentityManagement, - name: 'My New Page', - iconClass: 'fa fa-dashboard', - path: 'page', - invisible: false, - order: 3, - requiredPolicy: 'MyProjectName.MyNewPage' -} as Omit; - -this.config.dispatchAddRoute(newRoute); -// returns a state stream which emits after dispatch action is complete +{ + path: 'your-path', + data: { + routes: [ + { + path: '/your-path', + name: 'Your navigation', + order: 101, + iconClass: 'fas fa-question-circle', + requiredPolicy: 'permission key here', + }, + { + path: '/your-path/child', + name: 'Your child navigation', + parentName: 'Your navigation', + order: 1, + requiredPolicy: 'permission key here', + }, + ] as ABP.Route[], // can be imported from @abp/ng.core + }, +} ``` -`newRoute` 做为 `eIdentityRouteNames.IdentityManagement` 的子路由添加, url 设置为 `'/identity/page'`. +第二种方法的优点是你不必绑定到父/子结构,可以使用任何喜欢的路由. -新路由看起来像这样: +如上所述添加 `routes` 属性后,导航菜单看起来像这样: -![navigation-menu-via-config-state](./images/navigation-menu-via-config-state.png) +![navigation-menu-via-app-routing](./images/navigation-menu-via-app-routing.png) -## 如何修改一个导航元素 +## 如何修补或删除导航元素 -`DispatchPatchRouteByName` 方法通过名称查找路由,并使用二个参数传递的新配置替换存储中的配置. +`RoutesService` 的 `patch` 方法通过名称查找路由,并将配置替换为第二个参数传递的新配置. `remove` 方法会找到一个路由并将其连同其子路由一起删除. ```js -// this.config is instance of ConfigStateService -// eIdentityRouteNames enum can be imported from @abp/ng.identity +// this.routes is instance of RoutesService +// eThemeSharedRouteNames enum can be imported from @abp/ng.theme.shared + +const dashboardRouteConfig: ABP.Route = { + path: '/dashboard', + name: '::Menu:Dashboard', + parentName: '::Menu:Home', + order: 1, + layout: eLayoutType.application, +}; -const newRouteConfig: Partial = { +const newHomeRouteConfig: Partial = { iconClass: 'fas fa-home', - parentName: eIdentityRouteNames.Administration, + parentName: eThemeSharedRouteNames.Administration, order: 0, - children: [ - { - name: 'Dashboard', - path: 'dashboard', - }, - ], }; -this.config.dispatchPatchRouteByName('::Menu:Home', newRouteConfig); -// returns a state stream which emits after dispatch action is complete +this.routes.add([dashboardRouteConfig]); +this.routes.patch('::Menu:Home', newHomeRouteConfig); +this.routes.remove(['Your navigation']); ``` -* 根据给定的 `parentName` 将 _Home_ 导航移动到 _Administration_ 下拉框下. -* 添加了 icon. -* 指定了顺序. -* 添加了名为 _Dashboard_ 的子路由. +- 根据给定的 `parentName` 将 _Home_ 导航移动到 _Administration_ 下拉菜单下. +- 为 _Home_ 添加了图标. +- 指定 _Home_ 的顺序为列表的第一项. +- 为 _Home_ 添加了一个名为 _Dashboard_ 的子路由. +- 删除 _Your navigation_ 与其子路由. -修改后,导航元素看起来像这样: +上述操作后,新的菜单看起来如下: ![navigation-menu-after-patching](./images/navigation-menu-after-patching.png) @@ -184,7 +250,7 @@ export class AppComponent { } ``` -上面我们在菜单添加了一个搜索输入,最终UI如下:s +上面我们在菜单添加了一个搜索输入,最终UI如下: ![navigation-menu-search-input](./images/navigation-menu-search-input.png) diff --git a/docs/zh-Hans/UI/Angular/images/table-of-packages-to-update.png b/docs/zh-Hans/UI/Angular/images/table-of-packages-to-update.png new file mode 100644 index 0000000000..fc492d3740 Binary files /dev/null and b/docs/zh-Hans/UI/Angular/images/table-of-packages-to-update.png differ diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md index ceaa3d3d69..5ec349b8b6 100644 --- a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md @@ -1,3 +1,277 @@ -## Dynamic Forms +# 动态表单 -目前还没有文档. 你现在可以看到[组件演示](http://bootstrap-taghelpers.abp.io/Components/DynamicForms). \ No newline at end of file +`提示:` 在开始阅读本文档之前,请确保你已经看过并理解了[abp表单元素](Form-elements.md)文档. + +## 介绍 + +`abp-dynamic-form` 为给定c#模型创建bootstrap表单. + +基本用法: + +````xml + +```` + +Model: + +````csharp +public class DynamicFormsModel : PageModel +{ + [BindProperty] + public DetailedModel MyDetailedModel { get; set; } + + public List CountryList { get; set; } = new List + { + new SelectListItem { Value = "CA", Text = "Canada"}, + new SelectListItem { Value = "US", Text = "USA"}, + new SelectListItem { Value = "UK", Text = "United Kingdom"}, + new SelectListItem { Value = "RU", Text = "Russia"} + }; + + public void OnGet() + { + MyDetailedModel = new DetailedModel + { + Name = "", + Description = "Lorem ipsum dolor sit amet.", + IsActive = true, + Age = 65, + Day = DateTime.Now, + MyCarType = CarType.Coupe, + YourCarType = CarType.Sedan, + Country = "RU", + NeighborCountries = new List() { "UK", "CA" } + }; + } + + public class DetailedModel + { + [Required] + [Placeholder("Enter your name...")] + [Display(Name = "Name")] + public string Name { get; set; } + + [TextArea(Rows = 4)] + [Display(Name = "Description")] + [InputInfoText("Describe Yourself")] + public string Description { get; set; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Display(Name = "Is Active")] + public bool IsActive { get; set; } + + [Required] + [Display(Name = "Age")] + public int Age { get; set; } + + [Required] + [Display(Name = "My Car Type")] + public CarType MyCarType { get; set; } + + [Required] + [AbpRadioButton(Inline = true)] + [Display(Name = "Your Car Type")] + public CarType YourCarType { get; set; } + + [DataType(DataType.Date)] + [Display(Name = "Day")] + public DateTime Day { get; set; } + + [SelectItems(nameof(CountryList))] + [Display(Name = "Country")] + public string Country { get; set; } + + [SelectItems(nameof(CountryList))] + [Display(Name = "Neighbor Countries")] + public List NeighborCountries { get; set; } + } + + public enum CarType + { + Sedan, + Hatchback, + StationWagon, + Coupe + } +} +```` + +## Demo + +参阅 [动态表单demo页面](https://bootstrap-taghelpers.abp.io/Components/Dropdowns)查看示例. + +## Attributes + +### abp-model + +为动态表单设置c#模型,模型的属性以表单形式转化为输入. + +### submit-button + +可以为 `True` 或 `False`. + +如果为 `True`,则会在表单底部生成一个提交按钮. + +默认值是 `False`. + +### required-symbols + +可以为 `True` 或 `False`. + +如果为 `True`,则必需的输入将带有一个符号(*),表示它们是必需的. + +默认值是 `True`. + +## 表单内容布局 + +默认情况下,“`abp-dynamic-form` 会清除内部html并将inputs放入自身. 如果要向动态表单添加其他内容或将inputs放置到某些特定区域,可以使用``标签. 这个标签将被表单内容替换, 而 `abp-dynamic-form` 标签的内部html的其余部分将保持不变. + +用法: + +````xml + +
+ Some content.... +
+
+ +
+
+ Some more content.... +
+
+```` + +## 输入排序 + +`abp-dynamic-form` 通过 `DisplayOrder` attribute对属性进行排序,然后按模型类中的属性顺序进行排序. + +默认每个属性的 `DisplayOrder` attribute值是10000. + +参见以下示例: + +````csharp +public class OrderExampleModel +{ + [DisplayOrder(10004)] + public string Name{ get; set; } + + [DisplayOrder(10005)] + public string Surname{ get; set; } + + //Default 10000 + public string EmailAddress { get; set; } + + [DisplayOrder(10003)] + public string PhoneNumber { get; set; } + + [DisplayOrder(9999)] + public string City { get; set; } +} +```` + +在这个示例中,inputs字段顺序为: `City` > `EmailAddress` > `PhoneNumber` > `Name` > `Surname`. + +## 忽略属性 + +默认情况下, `abp-dynamic-form` 会为模型类中的每个属性生成输入. 如果要忽略属性请使用 `DynamicFormIgnore` attribute. + +参见以下示例: + +````csharp +public class MyModel +{ + public string Name { get; set; } + + [DynamicFormIgnore] + public string Surname { get; set; } +} +```` + +在这个示例中,不会为 `Surname` 属性生成输入. + +## 指示文本框,单选按钮组和组合框 + +如果你已经阅读了[表单元素文档](Form-elements.md),你会注意到在c#模型上 `abp-radio` 和 `abp-select` 标签非常相. 我们必须使用 `[AbpRadioButton()]` attribute来告诉 `abp-dynamic-form` 你的哪些属性是单选按钮组,哪些属性是组合框. + +参见以下示例: + +````xml + +```` + +Model: + +````csharp +public class DynamicFormsModel : PageModel +{ + [BindProperty] + public DetailedModel MyDetailedModel { get; set; } + + public List CountryList { get; set; } = new List + { + new SelectListItem { Value = "CA", Text = "Canada"}, + new SelectListItem { Value = "US", Text = "USA"}, + new SelectListItem { Value = "UK", Text = "United Kingdom"}, + new SelectListItem { Value = "RU", Text = "Russia"} + }; + + public void OnGet() + { + MyDetailedModel = new DetailedModel + { + ComboCarType = CarType.Coupe, + RadioCarType = CarType.Sedan, + ComboCountry = "RU", + RadioCountry = "UK" + }; + } + + public class DetailedModel + { + public CarType ComboCarType { get; set; } + + [AbpRadioButton(Inline = true)] + public CarType RadioCarType { get; set; } + + [SelectItems(nameof(CountryList))] + public string ComboCountry { get; set; } + + [AbpRadioButton()] + [SelectItems(nameof(CountryList))] + public string RadioCountry { get; set; } + } + + public enum CarType + { + Sedan, + Hatchback, + StationWagon, + Coupe + } +} +```` + +正如你上面的例子中看到: + +* 如果在**Enum**属性上使用 `[AbpRadioButton()]`,它将是一个单选按钮组. 否则它是组合框. +* 如果在属性上使用 `[SelectItems()]` 和 `[AbpRadioButton()]`,那么它将是一个单选按钮组. +* 如果只在属性上使用 `[SelectItems()]`,它将是一个组合框. +* 如果一个属性没有使用这些属性,它将是一个文本框. + +## 本地化 + +`abp-dynamic-form` 会处理本地化. + +默认情况下, 它将尝试查找 "DisplayName:{PropertyName}" 或 "{PropertyName}" 定位本地化键,并将定位值设置为label. + +你可以使用Asp.Net Core的 `[Display()]` attribute自行设置. 可以在此属性中使用本地化密钥. 请参阅以下示例: + +````csharp +[Display(Name = "Name")] +public string Name { get; set; } +```` \ No newline at end of file diff --git a/docs/zh-Hans/Unit-Of-Work.md b/docs/zh-Hans/Unit-Of-Work.md index 233467250b..0fbf5762f0 100644 --- a/docs/zh-Hans/Unit-Of-Work.md +++ b/docs/zh-Hans/Unit-Of-Work.md @@ -325,7 +325,7 @@ public async Task CreateAsync(string name) 如果你的目的只是在创建/更新/删除实体后保存更改,建议你使用 `autoSave` 选项,而不是手动使用 `CurrentUnitOfWork.SaveChangesAsync()`. -> **Note-1**: 当工作单元结束而没有任何错误时,所有更改都会自动保存. 所以除非确实需要,否则不要调用 `SaveChangesAsync()`. +> **Note-1**: 当工作单元结束而没有任何错误时,所有更改都会自动保存. 所以除非确实需要,否则不要调用 `SaveChangesAsync()` 和设置 `autoSave` 为 `true`. > > **Note-2**: 如果你使用 `Guid` 作为主键,则无需插入时保存来获取生成的id,因为 `Guid` 主键是在应用程序中设置的,创建新实体后立即可用. diff --git a/docs/zh-Hans/Virtual-File-System.md b/docs/zh-Hans/Virtual-File-System.md index 3f1e9c2236..6952c4d5da 100644 --- a/docs/zh-Hans/Virtual-File-System.md +++ b/docs/zh-Hans/Virtual-File-System.md @@ -2,109 +2,77 @@ 虚拟文件系统使得管理物理上不存在于文件系统中(磁盘)的文件成为可能. 它主要用于将(js, css, image...)文件嵌入到程序集中, 并在运行时将它们象物理文件一样使用. -### Volo.Abp.VirtualFileSystem nuget包 +## 安装 -Volo.Abp.VirtualFileSystem是虚拟文件系统的核心包. 使用程序包管理器控制台(PMC)将其安装到项目中: +> 大多数情况下你不需要手动安装这个包,因为[应用程序启动模板](Startup-Templates/Application.md)已经预先安装. -``` -Install-Package Volo.Abp.VirtualFileSystem -``` +[Volo.Abp.VirtualFileSystem](https://www.nuget.org/packages/Volo.Abp.VirtualFileSystem) 是虚拟文件系统的NuGet主页. -> 启动模板默认已经安装了此nuget包, 所以在大多数情况下你不需要手动安装它. +使用ABP CLIi添加包到你的项目: -然后你可以在module中添加 **AbpVirtualFileSystemModule** 依赖项: +* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI),如果你还没有安装. +* 在你想要添加 `Volo.Abp.VirtualFileSystem` 包的项目的 `.csproj` 文件目录打开命令行(终端). +* 运行 `abp add-package Volo.Abp.VirtualFileSystem` 命令. -```c# -using Volo.Abp.Modularity; -using Volo.Abp.VirtualFileSystem; +如果你想要手动安装,安装[Volo.Abp.VirtualFileSystem](https://www.nuget.org/packages/Volo.Abp.VirtualFileSystem)NuGet包到你的项目并且添加`[DependsOn(typeof(AbpVirtualFileSystemModule))]`到你项目的[ABP Module](Module-Development-Basics.md)类. -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpVirtualFileSystemModule))] - public class MyModule : AbpModule - { - //... - } -} -``` +## 与嵌入式文件工作 -#### 注册嵌入式文件 +### 嵌入文件 -要将文件嵌入到程序集中, 首先需要把该文件标记为嵌入式资源. 最简单的方式是在 **解决方案管理器** 中选择文件, 然后找到 **"属性"** 窗口将 **"生成操作"** 设置为 **"嵌入式资源"**. +要将文件嵌入到程序集中, 首先需要把该文件标记为**嵌入式资源**. 最简单的方式是在 **解决方案管理器** 中选择文件, 然后找到 **"属性"** 窗口将 **"生成操作"** 设置为 **"嵌入式资源"**. 例如: ![build-action-embedded-resource-sample](images/build-action-embedded-resource-sample.png) 如果需要添加多个文件, 这样做会很乏味. 作为选择, 你可以直接编辑 **.csproj** 文件: -````C# - - - - -```` +如果文件名包含一些特殊字符,在项目/程序集中嵌入文件可能会导致问题. 为了克服这个限制; + +1. 将[Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet包添加到包含嵌入式资源的项目中. +2. 添加 `true` 到 `.csproj` 文件的 `...` 部分中. 此配置以递归方式添加项目的 **MyResources** 文件夹下的所有文件(包括将来新添加的文件). -然后需要使用 `AbpVirtualFileSystemOptions` 来配置模块, 以便将嵌入式文件注册到虚拟文件系统. 例如: +> 尽管这两个步骤是可选的,并且ABP无需这些配置即可工作,但强烈建议你这样做. -````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; -using Volo.Abp.VirtualFileSystem; +### 配置AbpVirtualFileSystemOptions -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpVirtualFileSystemModule))] - public class MyModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - //Register all embedded files of this assembly to the virtual file system - options.FileSets.AddEmbedded("YourRootNameSpace"); - }); +使用 `AbpVirtualFileSystemOptions` [选项类](Options.md)可以在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中将嵌入式文件注册到虚拟文件系统. - //... - } - } -} -```` - -`AddEmbedded` 扩展方法需要一个类, 从给定类的程序集中查找所有嵌入文件, 并将它们注册到虚拟文件系统. 它还有更简洁的写法: +**示例: 添加嵌入式文件到虚拟文件系统** -````C# -options.FileSets.Add(new EmbeddedFileSet(typeof(MyModule).Assembly), "YourRootNameSpace"); +````csharp +Configure(options => +{ + options.FileSets.AddEmbedded(); +}); ```` -> "YourRootNameSpace" 是项目的根命名空间名字. 如果你的项目的根命名空间名字为空,则无需传递此参数. +`AddEmbedded` 扩展方法需要一个类, 从给定**类的程序集中查找所有嵌入文件**, 并将它们注册到虚拟文件系统. 通常将模块类作为通用参数传递. -#### 获取虚拟文件: IVirtualFileProvider +`AddEmbedded` 有两个可选参数; -将文件嵌入到程序集中并注册到虚拟文件系统后, 可以使用`IVirtualFileProvider`来获取文件或目录内容: +* `baseNamespace`: 仅在你没有配置上面说明的 `GenerateEmbeddedFilesManifest` 并且你的根名称空间不为空时,才可能需要这样做. 在这种情况下,请在此处设置你的根名称空间. +* `baseFolder`: 如果你不想公开项目中的所有嵌入式文件,只希望公开特定的文件夹(和子文件夹/文件),可以相对于项目根页面设置基本文件夹. -````C# -public class MyService -{ - private readonly IVirtualFileProvider _virtualFileProvider; +**示例: 添加项目中 `MyFiles` 目录下的文件** - public MyService(IVirtualFileProvider virtualFileProvider) - { - _virtualFileProvider = virtualFileProvider; - } +````csharp +Configure(options => +{ + options.FileSets.AddEmbedded( + baseNamespace: "Acme.BookStore.MyFiles", + baseFolder: "/MyFiles" + ); +}); +```` - public void Foo() - { - //Getting a single file - var file = _virtualFileProvider.GetFileInfo("/MyResources/js/test.js"); - var fileContent = file.ReadAsString(); //ReadAsString is an extension method of ABP +这个例子假设; - //Getting all files/directories under a directory - var directoryContents = _virtualFileProvider.GetDirectoryContents("/MyResources/js"); - } -} -```` +* 你的项目根(default)命令空间是 `Acme.BookStore`. +* 你的项目有一个名为 `MyFiles` 的目录. +* 你只想添加 `MyFiles` 目录到虚拟文件系统. #### 在开发过程中处理嵌入式文件 @@ -128,27 +96,61 @@ public class MyWebAppModule : AbpModule { Configure(options => { - //ReplaceEmbeddedByPhysical gets the root folder of the MyModule project options.FileSets.ReplaceEmbeddedByPhysical( - Path.Combine(hostingEnvironment.ContentRootPath, "..\\MyModuleProject") + Path.Combine( + hostingEnvironment.ContentRootPath, + string.Format( + "..{0}MyModuleProject", + Path.DirectorySeparatorChar + ) + ) ); }); } - - //... } } ```` 上面的代码假设`MyWebAppModule`和`MyModule`是Visual Studio解决方案中的两个不同的项目, `MyWebAppModule`依赖于`MyModule`. +> [应用程序启动模板]已经为本地化文件应用这个方法,所以当你更改一个本地化文件时,它会自动检测到更改. + +## IVirtualFileProvider + +将文件嵌入到程序集中并注册到虚拟文件系统后,可以使用 `IVirtualFileProvider` 接口来获取文件或目录内容: + +````C# +public class MyService +{ + private readonly IVirtualFileProvider _virtualFileProvider; + + public MyService(IVirtualFileProvider virtualFileProvider) + { + _virtualFileProvider = virtualFileProvider; + } + + public void Foo() + { + //Getting a single file + var file = _virtualFileProvider + .GetFileInfo("/MyResources/js/test.js"); + + var fileContent = file.ReadAsString(); + + //Getting all files/directories under a directory + var directoryContents = _virtualFileProvider + .GetDirectoryContents("/MyResources/js"); + } +} +```` + ### ASP.NET Core 集成 虚拟文件系统与 ASP.NET Core 无缝集成: * 虚拟文件可以像Web应用程序上的物理(静态)文件一样使用. * Js, css, 图像文件和所有其他Web内容可以嵌入到程序集中并像物理文件一样使用. -* 应用程序(或其他模块)可以覆盖模块的虚拟文件, 就像将具有相同名称和扩展名的文件放入虚拟文件的同一文件夹中一样. +* 应用程序(或其他模块)可以**覆盖模块的虚拟文件**, 就像将具有相同名称和扩展名的文件放入虚拟文件的同一文件夹中一样. #### 虚拟文件中间件 @@ -160,4 +162,14 @@ app.UseVirtualFiles(); 在静态文件中间件之后添加虚拟文件中间件, 使得通过在虚拟文件相同的位置放置物理文件, 从而用物理文件覆盖虚拟文件成为可能. -> 虚拟文件中间件可以虚拟wwwroot文件夹中的内容 - 就像静态文件一样. +> [应用程序启动模板](Startup-Templates/Application.md)已经配置了 `UseVirtualFiles()`. + +#### 静态虚拟文件夹 + +默认情况下,ASP.NET Core仅允许 `wwwroot` 文件夹包含客户端使用的静态文件. 当你使用 `UseVirtualFiles` 中间件时以下文件夹也可以包含静态文件: + +* Pages +* Views +* Themes + +这允许你可以在 `.cshtml` 文件附近添加 `.js`, `.css`... 文件,更易于开发和维护你的项目. diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 04da437b7b..66229da6da 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -368,6 +368,10 @@ { "text": "Angular", "items": [ + { + "text": "v2.x 到 v3 迁移指南", + "path": "UI/Angular/Migration-Guide-v3.md" + }, { "text": "服务代理", "path": "UI/Angular/Service-Proxies.md" diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index bd364b91b8..a3ee894572 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -307,6 +307,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Oracle", "src\Volo.Abp.EntityFrameworkCore.Oracle\Volo.Abp.EntityFrameworkCore.Oracle.csproj", "{1738845A-5348-4EB8-B736-CD1D22A808B4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Caching.StackExchangeRedis", "src\Volo.Abp.Caching.StackExchangeRedis\Volo.Abp.Caching.StackExchangeRedis.csproj", "{2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Caching.StackExchangeRedis.Tests", "test\Volo.Abp.Caching.StackExchangeRedis.Tests\Volo.Abp.Caching.StackExchangeRedis.Tests.csproj", "{60D0E384-965E-4F81-9D71-B28F419254FC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -913,6 +917,14 @@ Global {1738845A-5348-4EB8-B736-CD1D22A808B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {1738845A-5348-4EB8-B736-CD1D22A808B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {1738845A-5348-4EB8-B736-CD1D22A808B4}.Release|Any CPU.Build.0 = Release|Any CPU + {2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4}.Release|Any CPU.Build.0 = Release|Any CPU + {60D0E384-965E-4F81-9D71-B28F419254FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60D0E384-965E-4F81-9D71-B28F419254FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60D0E384-965E-4F81-9D71-B28F419254FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60D0E384-965E-4F81-9D71-B28F419254FC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1068,6 +1080,8 @@ Global {C44242F7-D55D-4867-AAF4-A786E404312E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {A80E9A0B-8932-4B5D-83FB-6751708FD484} = {447C8A77-E5F0-4538-8687-7383196D04EA} {1738845A-5348-4EB8-B736-CD1D22A808B4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {60D0E384-965E-4F81-9D71-B28F419254FC} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json deleted file mode 100644 index fcbf86698d..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53450/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Authentication.OAuth": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53451/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json deleted file mode 100644 index 67c494aa28..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53760/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.MultiTenancy": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53763/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json deleted file mode 100644 index e17b14d87f..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:52302/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.Client": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:52303/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs index 2197d7b08e..ce08bebe87 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs @@ -14,5 +14,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations public string UserName { get; set; } public string Email { get; set; } + + public string[] Roles { get; set; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Properties/launchSettings.json deleted file mode 100644 index c2dcf8fae6..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:61183/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.UI.Bootstrap": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Properties/launchSettings.json deleted file mode 100644 index 7bcd0a8aff..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53762/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.UI.Bundling": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53765/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Properties/launchSettings.json deleted file mode 100644 index 6870af4120..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:56918/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.UI.TenantSwitch": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:56919/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Properties/launchSettings.json deleted file mode 100644 index 4ae9c1d2f1..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53766/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.UI.Packages": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53768/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Uppy/UppyScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Uppy/UppyScriptContributor.cs new file mode 100644 index 0000000000..691f377c6c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Uppy/UppyScriptContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Uppy +{ + public class UppyScriptContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/libs/uppy/uppy.min.js"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Uppy/UppyStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Uppy/UppyStyleContributor.cs new file mode 100644 index 0000000000..c10509515d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Uppy/UppyStyleContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Uppy +{ + public class UppyStyleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/libs/uppy/uppy.min.css"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Properties/launchSettings.json deleted file mode 100644 index 763367a2a8..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:50398/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:50399/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj index c390cfe58c..206224f918 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj @@ -22,8 +22,6 @@ - - diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj index 078563ace3..8c098c3300 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj @@ -18,8 +18,6 @@ - - diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/AbpPageSearchBoxViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/AbpPageSearchBoxViewComponent.cs index d14c92c62e..a21a3c51cd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/AbpPageSearchBoxViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/AbpPageSearchBoxViewComponent.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Pages.Shared.Components.AbpPageSearchBox { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/Default.cshtml index d7a2e893d4..65853b9d73 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/Default.cshtml @@ -1,13 +1,17 @@ @using Localization.Resources.AbpUi @using Microsoft.Extensions.Localization + @inject IStringLocalizer L +
- +
- +
-
\ No newline at end of file + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/_ViewImports.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/_ViewImports.cshtml new file mode 100644 index 0000000000..c1da1f5f10 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageSearchBox/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Properties/launchSettings.json deleted file mode 100644 index f928a8ea06..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51544/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:51545/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj index 71e1257a4d..4b0fbc24cf 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj @@ -23,9 +23,7 @@ - - diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Properties/launchSettings.json deleted file mode 100644 index e9779c3c37..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53761/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.UI.Widgets": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53764/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Properties/launchSettings.json deleted file mode 100644 index 3b3fd9ea46..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55913/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc.UI": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc/Properties/launchSettings.json deleted file mode 100644 index 3c84fd21f2..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53767/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Mvc": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53769/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 263178e0ed..e3cee4e72b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -16,6 +16,7 @@ using System.Net; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; @@ -25,6 +26,7 @@ using Microsoft.Extensions.Localization; using Volo.Abp.ApiVersioning; using Volo.Abp.AspNetCore.Mvc.ApiExploring; using Volo.Abp.AspNetCore.Mvc.Conventions; +using Volo.Abp.AspNetCore.Mvc.DataAnnotations; using Volo.Abp.AspNetCore.Mvc.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Json; using Volo.Abp.AspNetCore.Mvc.Localization; @@ -164,6 +166,9 @@ namespace Volo.Abp.AspNetCore.Mvc partManager.FeatureProviders.Add(new AbpConventionalControllerFeatureProvider(application)); partManager.ApplicationParts.AddIfNotContains(typeof(AbpAspNetCoreMvcModule).Assembly); + context.Services.Replace(ServiceDescriptor.Singleton()); + context.Services.AddSingleton(); + Configure(mvcOptions => { mvcOptions.AddAbp(context.Services); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 4aa183a43d..59166d3e1d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -5,7 +5,6 @@ using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; using Volo.Abp.AspNetCore.Mvc.Features; using Volo.Abp.AspNetCore.Mvc.ModelBinding; -using Volo.Abp.AspNetCore.Mvc.ModelBinding.Metadata; using Volo.Abp.AspNetCore.Mvc.Response; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index 5aa8f07059..54815d6ff5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -117,7 +117,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations Id = _currentUser.Id, TenantId = _currentUser.TenantId, UserName = _currentUser.UserName, - Email = _currentUser.Email + Email = _currentUser.Email, + Roles = _currentUser.Roles }; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs new file mode 100644 index 0000000000..c9b7cb21b3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.Extensions.Localization; +using Volo.Abp.Validation; + +namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations +{ + public class AbpValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider + { + private readonly ValidationAttributeAdapterProvider _defaultAdapter; + + public AbpValidationAttributeAdapterProvider(ValidationAttributeAdapterProvider defaultAdapter) + { + _defaultAdapter = defaultAdapter; + } + + public virtual IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer) + { + var type = attribute.GetType(); + + if (type == typeof(DynamicStringLengthAttribute)) + { + return new DynamicStringLengthAttributeAdapter((DynamicStringLengthAttribute) attribute, stringLocalizer); + } + + if (type == typeof(DynamicMaxLengthAttribute)) + { + return new DynamicMaxLengthAttributeAdapter((DynamicMaxLengthAttribute) attribute, stringLocalizer); + } + + return _defaultAdapter.GetAttributeAdapter(attribute, stringLocalizer); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicMaxLengthAttributeAdapter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicMaxLengthAttributeAdapter.cs new file mode 100644 index 0000000000..f8193a2e79 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicMaxLengthAttributeAdapter.cs @@ -0,0 +1,46 @@ +using System.Globalization; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using Volo.Abp.Validation; + +namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations +{ + public class DynamicMaxLengthAttributeAdapter : AttributeAdapterBase + { + private readonly string _max; + + public DynamicMaxLengthAttributeAdapter( + DynamicMaxLengthAttribute attribute, + IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + _max = Attribute.Length.ToString(CultureInfo.InvariantCulture); + } + + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + Check.NotNull(validationContext, nameof(validationContext)); + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.Length + ); + } + + public override void AddValidation(ClientModelValidationContext context) + { + Check.NotNull(context, nameof(context)); + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-maxlength", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-maxlength-max", _max); + + if (Attribute.Length != int.MaxValue) + { + MergeAttribute(context.Attributes, "data-val-maxlength-max", _max); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicStringLengthAttributeAdapter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicStringLengthAttributeAdapter.cs new file mode 100644 index 0000000000..d4c46b85f3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicStringLengthAttributeAdapter.cs @@ -0,0 +1,54 @@ +using System; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using Volo.Abp.Validation; + +namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations +{ + public class DynamicStringLengthAttributeAdapter : AttributeAdapterBase + { + private readonly string _max; + private readonly string _min; + + public DynamicStringLengthAttributeAdapter( + DynamicStringLengthAttribute attribute, + IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + _max = Attribute.MaximumLength.ToString(CultureInfo.InvariantCulture); + _min = Attribute.MinimumLength.ToString(CultureInfo.InvariantCulture); + } + + public override void AddValidation(ClientModelValidationContext context) + { + Check.NotNull(context, nameof(context)); + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-length", GetErrorMessage(context)); + + if (Attribute.MaximumLength != int.MaxValue) + { + MergeAttribute(context.Attributes, "data-val-length-max", _max); + } + + if (Attribute.MinimumLength != 0) + { + MergeAttribute(context.Attributes, "data-val-length-min", _min); + } + } + + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + Check.NotNull(validationContext, nameof(validationContext)); + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.MaximumLength, + Attribute.MinimumLength + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json deleted file mode 100644 index cb1bd6546c..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:61851/", - "sslPort": 44301 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.Serilog": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json deleted file mode 100644 index 418c7f2621..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53900/", - "sslPort": 44362 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.SignalR": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json deleted file mode 100644 index fca5708e7b..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53783/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore.TestBase": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53784/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json deleted file mode 100644 index 4098e788cd..0000000000 --- a/framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53374/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.AspNetCore": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53375/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs index 7823502013..9cfc748c4e 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Volo.Abp.Modularity; using Volo.Abp.Quartz; @@ -18,6 +19,11 @@ namespace Volo.Abp.BackgroundWorkers.Quartz context.Services.AddConventionalRegistrar(new AbpQuartzConventionalRegistrar()); } + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddSingleton(typeof(QuartzPeriodicBackgroundWorkerAdapter<>)); + } + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) { var options = context.ServiceProvider.GetService>().Value; diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/IQuartzBackgroundWorkerAdapter.cs b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/IQuartzBackgroundWorkerAdapter.cs new file mode 100644 index 0000000000..dd6a3019b2 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/IQuartzBackgroundWorkerAdapter.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BackgroundWorkers.Quartz +{ + public interface IQuartzBackgroundWorkerAdapter : IQuartzBackgroundWorker + { + void BuildWorker(IBackgroundWorker worker); + } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerAdapter.cs b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerAdapter.cs new file mode 100644 index 0000000000..96cf3988bd --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerAdapter.cs @@ -0,0 +1,88 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Quartz; +using Volo.Abp.Threading; + +namespace Volo.Abp.BackgroundWorkers.Quartz +{ + public class QuartzPeriodicBackgroundWorkerAdapter : QuartzBackgroundWorkerBase, + IQuartzBackgroundWorkerAdapter + where TWorker : IBackgroundWorker + { + private readonly MethodInfo _doWorkAsyncMethod; + private readonly MethodInfo _doWorkMethod; + + public QuartzPeriodicBackgroundWorkerAdapter() + { + AutoRegister = false; + + _doWorkAsyncMethod = typeof(TWorker).GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic); + _doWorkMethod = typeof(TWorker).GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic); + + } + + public void BuildWorker(IBackgroundWorker worker) + { + int? period; + var workerType = worker.GetType(); + + if (worker is AsyncPeriodicBackgroundWorkerBase || worker is PeriodicBackgroundWorkerBase) + { + if (typeof(TWorker) != worker.GetType()) + { + throw new ArgumentException($"{nameof(worker)} type is different from the generic type"); + } + + var timer = (AbpTimer) worker.GetType().GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker); + period = timer?.Period; + } + else + { + return; + } + + if (period == null) + { + return; + } + + JobDetail = JobBuilder + .Create>() + .WithIdentity(workerType.FullName) + .Build(); + Trigger = TriggerBuilder.Create() + .WithIdentity(workerType.FullName) + .WithSimpleSchedule(builder => builder.WithInterval(TimeSpan.FromMilliseconds(period.Value)).RepeatForever()) + .Build(); + } + + public override async Task Execute(IJobExecutionContext context) + { + var worker = (IBackgroundWorker) ServiceProvider.GetService(typeof(TWorker)); + var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider); + + switch (worker) + { + case AsyncPeriodicBackgroundWorkerBase asyncWorker: + { + if (_doWorkAsyncMethod != null) + { + await (Task) _doWorkAsyncMethod.Invoke(asyncWorker, new object[] {workerContext}); + } + + break; + } + case PeriodicBackgroundWorkerBase syncWorker: + { + if (_doWorkMethod != null) + { + _doWorkMethod.Invoke(syncWorker, new object[] {workerContext}); + } + + break; + } + } + } + } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerManager.cs index 35359b1d27..d670c1db98 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerManager.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Quartz; using Volo.Abp.DependencyInjection; @@ -50,6 +51,19 @@ namespace Volo.Abp.BackgroundWorkers.Quartz await DefaultScheduleJobAsync(quartzWork); } } + else + { + var adapterType = typeof(QuartzPeriodicBackgroundWorkerAdapter<>).MakeGenericType(worker.GetType()); + + var workerAdapter = Activator.CreateInstance(adapterType) as IQuartzBackgroundWorkerAdapter; + + workerAdapter?.BuildWorker(worker); + + if (workerAdapter?.Trigger != null) + { + await DefaultScheduleJobAsync(workerAdapter); + } + } } protected virtual async Task DefaultScheduleJobAsync(IQuartzBackgroundWorker quartzWork) @@ -66,4 +80,4 @@ namespace Volo.Abp.BackgroundWorkers.Quartz } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs index 945c930175..3b3cbc4743 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs @@ -15,6 +15,7 @@ namespace Volo.Abp.BlobStoring.Azure Action azureConfigureAction) { containerConfiguration.ProviderType = typeof(AzureBlobProvider); + containerConfiguration.NamingNormalizers.TryAdd(); azureConfigureAction(new AzureBlobProviderConfiguration(containerConfiguration)); diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobNamingNormalizer.cs new file mode 100644 index 0000000000..43b479e08e --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobNamingNormalizer.cs @@ -0,0 +1,52 @@ +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Azure +{ + public class AzureBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency + { + /// + ///https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names + /// + public virtual string NormalizeContainerName(string containerName) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); + + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); + + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) + { + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } + } + + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } + + return containerName; + } + + /// + ///https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#blob-names + /// + public virtual string NormalizeBlobName(string blobName) + { + return blobName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobContainerConfigurationExtensions.cs index 24357554eb..2bb818da56 100644 --- a/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobContainerConfigurationExtensions.cs +++ b/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobContainerConfigurationExtensions.cs @@ -9,16 +9,17 @@ namespace Volo.Abp.BlobStoring.FileSystem { return new FileSystemBlobProviderConfiguration(containerConfiguration); } - + public static BlobContainerConfiguration UseFileSystem( this BlobContainerConfiguration containerConfiguration, Action fileSystemConfigureAction) { containerConfiguration.ProviderType = typeof(FileSystemBlobProvider); - + containerConfiguration.NamingNormalizers.TryAdd(); + fileSystemConfigureAction(new FileSystemBlobProviderConfiguration(containerConfiguration)); - + return containerConfiguration; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobNamingNormalizer.cs new file mode 100644 index 0000000000..f7d273dca7 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobNamingNormalizer.cs @@ -0,0 +1,41 @@ +using System; +using System.Runtime; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.FileSystem +{ + public class FileSystemBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency + { + private readonly IOSPlatformProvider _iosPlatformProvider; + + public FileSystemBlobNamingNormalizer(IOSPlatformProvider iosPlatformProvider) + { + _iosPlatformProvider = iosPlatformProvider; + } + + public virtual string NormalizeContainerName(string containerName) + { + return Normalize(containerName); + } + + public virtual string NormalizeBlobName(string blobName) + { + return Normalize(blobName); + } + + protected virtual string Normalize(string fileName) + { + var os = _iosPlatformProvider.GetCurrentOSPlatform(); + if (os == OSPlatform.Windows) + { + // A filename cannot contain any of the following characters: \ / : * ? " < > | + // In order to support the directory included in the blob name, remove / and \ + fileName = Regex.Replace(fileName, "[:\\*\\?\"<>\\|]", string.Empty); + } + + return fileName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs index d165a40316..cd2287e964 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs @@ -1,7 +1,9 @@ using System; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -84,18 +86,22 @@ namespace Volo.Abp.BlobStoring protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected IServiceProvider ServiceProvider { get; } + public BlobContainer( string containerName, BlobContainerConfiguration configuration, IBlobProvider provider, ICurrentTenant currentTenant, - ICancellationTokenProvider cancellationTokenProvider) + ICancellationTokenProvider cancellationTokenProvider, + IServiceProvider serviceProvider) { ContainerName = containerName; Configuration = configuration; Provider = provider; CurrentTenant = currentTenant; CancellationTokenProvider = cancellationTokenProvider; + ServiceProvider = serviceProvider; } public virtual async Task SaveAsync( @@ -106,11 +112,13 @@ namespace Volo.Abp.BlobStoring { using (CurrentTenant.Change(GetTenantIdOrNull())) { + var (normalizedContainerName, normalizedBlobName) = NormalizeNaming(ContainerName, name); + await Provider.SaveAsync( new BlobProviderSaveArgs( - ContainerName, + normalizedContainerName, Configuration, - name, + normalizedBlobName, stream, overrideExisting, CancellationTokenProvider.FallbackToProvider(cancellationToken) @@ -125,11 +133,14 @@ namespace Volo.Abp.BlobStoring { using (CurrentTenant.Change(GetTenantIdOrNull())) { + var (normalizedContainerName, normalizedBlobName) = + NormalizeNaming(ContainerName, name); + return await Provider.DeleteAsync( new BlobProviderDeleteArgs( - ContainerName, + normalizedContainerName, Configuration, - name, + normalizedBlobName, CancellationTokenProvider.FallbackToProvider(cancellationToken) ) ); @@ -142,11 +153,14 @@ namespace Volo.Abp.BlobStoring { using (CurrentTenant.Change(GetTenantIdOrNull())) { + var (normalizedContainerName, normalizedBlobName) = + NormalizeNaming(ContainerName, name); + return await Provider.ExistsAsync( new BlobProviderExistsArgs( - ContainerName, + normalizedContainerName, Configuration, - name, + normalizedBlobName, CancellationTokenProvider.FallbackToProvider(cancellationToken) ) ); @@ -158,7 +172,7 @@ namespace Volo.Abp.BlobStoring CancellationToken cancellationToken = default) { var stream = await GetOrNullAsync(name, cancellationToken); - + if (stream == null) { //TODO: Consider to throw some type of "not found" exception and handle on the HTTP status side @@ -175,11 +189,14 @@ namespace Volo.Abp.BlobStoring { using (CurrentTenant.Change(GetTenantIdOrNull())) { + var (normalizedContainerName, normalizedBlobName) = + NormalizeNaming(ContainerName, name); + return await Provider.GetOrNullAsync( new BlobProviderGetArgs( - ContainerName, + normalizedContainerName, Configuration, - name, + normalizedBlobName, CancellationTokenProvider.FallbackToProvider(cancellationToken) ) ); @@ -195,5 +212,28 @@ namespace Volo.Abp.BlobStoring return CurrentTenant.Id; } + + protected virtual (string, string) NormalizeNaming(string containerName, string blobName) + { + if (!Configuration.NamingNormalizers.Any()) + { + return (containerName, blobName); + } + + using (var scope = ServiceProvider.CreateScope()) + { + foreach (var normalizerType in Configuration.NamingNormalizers) + { + var normalizer = scope.ServiceProvider + .GetRequiredService(normalizerType) + .As(); + + containerName = normalizer.NormalizeContainerName(containerName); + blobName = normalizer.NormalizeBlobName(blobName); + } + + return (containerName, blobName); + } + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfiguration.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfiguration.cs index 46f13a1c3d..7ca94b4b42 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfiguration.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfiguration.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using JetBrains.Annotations; +using Volo.Abp.Collections; namespace Volo.Abp.BlobStoring { @@ -18,17 +19,20 @@ namespace Volo.Abp.BlobStoring /// then the container is shared by all tenants in the system. /// /// This can be true even if your application is not multi-tenant. - /// + /// /// Default: true. /// public bool IsMultiTenant { get; set; } = true; + public ITypeList NamingNormalizers { get; } + [NotNull] private readonly Dictionary _properties; [CanBeNull] private readonly BlobContainerConfiguration _fallbackConfiguration; public BlobContainerConfiguration(BlobContainerConfiguration fallbackConfiguration = null) { + NamingNormalizers = new TypeList(); _fallbackConfiguration = fallbackConfiguration; _properties = new Dictionary(); } @@ -68,4 +72,4 @@ namespace Volo.Abp.BlobStoring return this; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs index ab27aab01f..7a4da39ef9 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs @@ -1,4 +1,5 @@ -using Volo.Abp.DependencyInjection; +using System; +using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -7,36 +8,41 @@ namespace Volo.Abp.BlobStoring public class BlobContainerFactory : IBlobContainerFactory, ITransientDependency { protected IBlobProviderSelector ProviderSelector { get; } - + protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } protected ICurrentTenant CurrentTenant { get; } - + protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected IServiceProvider ServiceProvider { get; } + public BlobContainerFactory( IBlobContainerConfigurationProvider configurationProvider, ICurrentTenant currentTenant, - ICancellationTokenProvider cancellationTokenProvider, - IBlobProviderSelector providerSelector) + ICancellationTokenProvider cancellationTokenProvider, + IBlobProviderSelector providerSelector, + IServiceProvider serviceProvider) { ConfigurationProvider = configurationProvider; CurrentTenant = currentTenant; CancellationTokenProvider = cancellationTokenProvider; ProviderSelector = providerSelector; + ServiceProvider = serviceProvider; } - + public virtual IBlobContainer Create(string name) { var configuration = ConfigurationProvider.Get(name); - + return new BlobContainer( name, configuration, ProviderSelector.Get(name), CurrentTenant, - CancellationTokenProvider + CancellationTokenProvider, + ServiceProvider ); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobNamingNormalizer.cs new file mode 100644 index 0000000000..2bdc03507d --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobNamingNormalizer.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.BlobStoring +{ + public interface IBlobNamingNormalizer + { + string NormalizeContainerName(string containerName); + + string NormalizeBlobName(string blobName); + } +} diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/FodyWeavers.xml b/framework/src/Volo.Abp.Caching.StackExchangeRedis/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/FodyWeavers.xsd b/framework/src/Volo.Abp.Caching.StackExchangeRedis/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj new file mode 100644 index 0000000000..c362c16bff --- /dev/null +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.0 + Volo.Abp.Caching.StackExchangeRedis + Volo.Abp.Caching.StackExchangeRedis + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs new file mode 100644 index 0000000000..b0f7d68ee4 --- /dev/null +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Caching.StackExchangeRedis +{ + [DependsOn( + typeof(AbpCachingModule) + )] + public class AbpCachingStackExchangeRedisModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + context.Services.AddStackExchangeRedisCache(options => + { + var redisConfiguration = configuration["Redis:Configuration"]; + if (!redisConfiguration.IsNullOrEmpty()) + { + options.Configuration = configuration["Redis:Configuration"]; + } + }); + + context.Services.Replace(ServiceDescriptor.Singleton()); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs new file mode 100644 index 0000000000..c3cb34f30c --- /dev/null +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.StackExchangeRedis; +using Microsoft.Extensions.Options; +using StackExchange.Redis; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Caching.StackExchangeRedis +{ + [DisableConventionalRegistration] + public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems + { + protected static readonly string SetScript; + protected static readonly string AbsoluteExpirationKey; + protected static readonly string SlidingExpirationKey; + protected static readonly string DataKey; + protected static readonly long NotPresent; + + private static readonly FieldInfo RedisDatabaseField; + private static readonly MethodInfo ConnectMethod; + private static readonly MethodInfo ConnectAsyncMethod; + private static readonly MethodInfo MapMetadataMethod; + private static readonly MethodInfo GetAbsoluteExpirationMethod; + private static readonly MethodInfo GetExpirationInSecondsMethod; + + protected IDatabase RedisDatabase => GetRedisDatabase(); + private IDatabase _redisDatabase; + + protected string Instance { get; } + + static AbpRedisCache() + { + var type = typeof(RedisCache); + + RedisDatabaseField = type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic); + + ConnectMethod = type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic); + + ConnectAsyncMethod = type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic); + + MapMetadataMethod = type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic); + + GetAbsoluteExpirationMethod = type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic); + + GetExpirationInSecondsMethod = type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic); + + SetScript = type.GetField("SetScript", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + + AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + + SlidingExpirationKey = type.GetField("SlidingExpirationKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + + DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + + NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).To(); + } + + public AbpRedisCache(IOptions optionsAccessor) + : base(optionsAccessor) + { + Instance = optionsAccessor.Value.InstanceName ?? string.Empty; + } + + protected virtual void Connect() + { + if (GetRedisDatabase() != null) + { + return; + } + + ConnectMethod.Invoke(this, Array.Empty()); + } + + protected virtual Task ConnectAsync(CancellationToken token = default) + { + if (GetRedisDatabase() != null) + { + return Task.CompletedTask; + } + + return (Task) ConnectAsyncMethod.Invoke(this, new object[] {token}); + } + + public byte[][] GetMany( + IEnumerable keys) + { + keys = Check.NotNull(keys, nameof(keys)); + + return GetAndRefreshMany(keys, true); + } + + public async Task GetManyAsync( + IEnumerable keys, + CancellationToken token = default) + { + keys = Check.NotNull(keys, nameof(keys)); + + return await GetAndRefreshManyAsync(keys, true, token); + } + + public void SetMany( + IEnumerable> items, + DistributedCacheEntryOptions options) + { + Connect(); + + Task.WaitAll(PipelineSetMany(items, options)); + } + + public async Task SetManyAsync( + IEnumerable> items, + DistributedCacheEntryOptions options, + CancellationToken token = default) + { + token.ThrowIfCancellationRequested(); + + await ConnectAsync(token); + + await Task.WhenAll(PipelineSetMany(items, options)); + } + + protected virtual byte[][] GetAndRefreshMany( + IEnumerable keys, + bool getData) + { + Connect(); + + var keyArray = keys.Select(key => Instance + key).ToArray(); + RedisValue[][] results; + + if (getData) + { + results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey, + SlidingExpirationKey, DataKey); + } + else + { + results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey, + SlidingExpirationKey); + } + + Task.WaitAll(PipelineRefreshManyAndOutData(keyArray, results, out var bytes)); + + return bytes; + } + + protected virtual async Task GetAndRefreshManyAsync( + IEnumerable keys, + bool getData, + CancellationToken token = default) + { + token.ThrowIfCancellationRequested(); + + await ConnectAsync(token); + + var keyArray = keys.Select(key => Instance + key).ToArray(); + RedisValue[][] results; + + if (getData) + { + results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, + SlidingExpirationKey, DataKey); + } + else + { + results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, + SlidingExpirationKey); + } + + await Task.WhenAll(PipelineRefreshManyAndOutData(keyArray, results, out var bytes)); + + return bytes; + } + + protected virtual Task[] PipelineRefreshManyAndOutData( + string[] keys, + RedisValue[][] results, + out byte[][] bytes) + { + bytes = new byte[keys.Length][]; + var tasks = new Task[keys.Length]; + + for (var i = 0; i < keys.Length; i++) + { + if (results[i].Length >= 2) + { + MapMetadata(results[i], out DateTimeOffset? absExpr, out TimeSpan? sldExpr); + + if (sldExpr.HasValue) + { + TimeSpan? expr; + + if (absExpr.HasValue) + { + var relExpr = absExpr.Value - DateTimeOffset.Now; + expr = relExpr <= sldExpr.Value ? relExpr : sldExpr; + } + else + { + expr = sldExpr; + } + + tasks[i] = RedisDatabase.KeyExpireAsync(keys[i], expr); + } + else + { + tasks[i] = Task.CompletedTask; + } + } + + if (results[i].Length >= 3 && results[i][2].HasValue) + { + bytes[i] = results[i][2]; + } + else + { + bytes[i] = null; + } + } + + return tasks; + } + + protected virtual Task[] PipelineSetMany( + IEnumerable> items, + DistributedCacheEntryOptions options) + { + items = Check.NotNull(items, nameof(items)); + options = Check.NotNull(options, nameof(options)); + + var itemArray = items.ToArray(); + var tasks = new Task[itemArray.Length]; + var creationTime = DateTimeOffset.UtcNow; + var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); + + for (var i = 0; i < itemArray.Length; i++) + { + tasks[i] = RedisDatabase.ScriptEvaluateAsync(SetScript, new RedisKey[] {Instance + itemArray[i].Key}, + new RedisValue[] + { + absoluteExpiration?.Ticks ?? NotPresent, + options.SlidingExpiration?.Ticks ?? NotPresent, + GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, + itemArray[i].Value + }); + } + + return tasks; + } + + protected virtual void MapMetadata( + RedisValue[] results, + out DateTimeOffset? absoluteExpiration, + out TimeSpan? slidingExpiration) + { + var parameters = new object[] {results, null, null}; + MapMetadataMethod.Invoke(this, parameters); + + absoluteExpiration = (DateTimeOffset?) parameters[1]; + slidingExpiration = (TimeSpan?) parameters[2]; + } + + protected virtual long? GetExpirationInSeconds( + DateTimeOffset creationTime, + DateTimeOffset? absoluteExpiration, + DistributedCacheEntryOptions options) + { + return (long?) GetExpirationInSecondsMethod.Invoke(null, + new object[] {creationTime, absoluteExpiration, options}); + } + + protected virtual DateTimeOffset? GetAbsoluteExpiration( + DateTimeOffset creationTime, + DistributedCacheEntryOptions options) + { + return (DateTimeOffset?) GetAbsoluteExpirationMethod.Invoke(null, new object[] {creationTime, options}); + } + + private IDatabase GetRedisDatabase() + { + if (_redisDatabase == null) + { + _redisDatabase = RedisDatabaseField.GetValue(this) as IDatabase; + } + + return _redisDatabase; + } + } +} diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisExtensions.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisExtensions.cs new file mode 100644 index 0000000000..ad077a7440 --- /dev/null +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisExtensions.cs @@ -0,0 +1,47 @@ +using System.Linq; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace Volo.Abp.Caching.StackExchangeRedis +{ + public static class AbpRedisExtensions + { + public static RedisValue[][] HashMemberGetMany( + this IDatabase cache, + string[] keys, + params string[] members) + { + var tasks = new Task[keys.Length]; + var fields = members.Select(member => (RedisValue) member).ToArray(); + var results = new RedisValue[keys.Length][]; + + for (var i = 0; i < keys.Length; i++) + { + tasks[i] = cache.HashGetAsync((RedisKey) keys[i], fields); + } + + for (var i = 0; i < tasks.Length; i++) + { + results[i] = cache.Wait(tasks[i]); + } + + return results; + } + + public static async Task HashMemberGetManyAsync( + this IDatabase cache, + string[] keys, + params string[] members) + { + var tasks = new Task[keys.Length]; + var fields = members.Select(member => (RedisValue) member).ToArray(); + + for (var i = 0; i < keys.Length; i++) + { + tasks[i] = cache.HashGetAsync((RedisKey) keys[i], fields); + } + + return await Task.WhenAll(tasks); + } + } +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 267ab259f5..5bb1dd2481 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -28,16 +31,16 @@ namespace Volo.Abp.Caching IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, IHybridServiceScopeFactory serviceScopeFactory) : base( - distributedCacheOption: distributedCacheOption, - cache: cache, - cancellationTokenProvider: cancellationTokenProvider, - serializer: serializer, - keyNormalizer: keyNormalizer, - serviceScopeFactory: serviceScopeFactory) + distributedCacheOption: distributedCacheOption, + cache: cache, + cancellationTokenProvider: cancellationTokenProvider, + serializer: serializer, + keyNormalizer: keyNormalizer, + serviceScopeFactory: serviceScopeFactory) { } - } + /// /// Represents a distributed cache of type. /// Uses a generic cache key type of type. @@ -148,19 +151,151 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + HandleException(ex); return null; } throw; } - if (cachedBytes == null) + return ToCacheItem(cachedBytes); + } + + public virtual KeyValuePair[] GetMany( + IEnumerable keys, + bool? hideErrors = null) + { + var keyArray = keys.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) { - return null; + return GetManyFallback( + keyArray, + hideErrors + ); } - return Serializer.Deserialize(cachedBytes); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + byte[][] cachedBytes; + + try + { + cachedBytes = cacheSupportsMultipleItems.GetMany(keyArray.Select(NormalizeKey)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return ToCacheItemsWithDefaultValues(keyArray); + } + + throw; + } + + return ToCacheItems(cachedBytes, keyArray); + } + + protected virtual KeyValuePair[] GetManyFallback( + TCacheKey[] keys, + bool? hideErrors = null) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + return keys + .Select(key => new KeyValuePair( + key, + Get(key, hideErrors: false) + ) + ).ToArray(); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return ToCacheItemsWithDefaultValues(keys); + } + + throw; + } + } + + public virtual async Task[]> GetManyAsync( + IEnumerable keys, + bool? hideErrors = null, + CancellationToken token = default) + { + var keyArray = keys.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) + { + return await GetManyFallbackAsync( + keyArray, + hideErrors, + token + ); + } + + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + byte[][] cachedBytes; + + try + { + cachedBytes = await cacheSupportsMultipleItems.GetManyAsync( + keyArray.Select(NormalizeKey), + CancellationTokenProvider.FallbackToProvider(token) + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return ToCacheItemsWithDefaultValues(keyArray); + } + + throw; + } + + return ToCacheItems(cachedBytes, keyArray); + } + + protected virtual async Task[]> GetManyFallbackAsync( + TCacheKey[] keys, + bool? hideErrors = null, + CancellationToken token = default) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + var result = new List>(); + + foreach (var key in keys) + { + result.Add(new KeyValuePair( + key, + await GetAsync(key, false, token)) + ); + } + + return result.ToArray(); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return ToCacheItemsWithDefaultValues(keys); + } + + throw; + } } /// @@ -307,7 +442,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + HandleException(ex); return; } @@ -354,14 +489,156 @@ namespace Volo.Abp.Caching } } - /// - /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. + public void SetMany( + IEnumerable> items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null) + { + var itemsArray = items.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) + { + SetManyFallback( + itemsArray, + options, + hideErrors + ); + + return; + } + + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + cacheSupportsMultipleItems.SetMany( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } + } + + protected virtual void SetManyFallback( + KeyValuePair[] items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + foreach (var item in items) + { + Set( + item.Key, + item.Value, + options: options, + hideErrors: false + ); + } + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } + } + + public virtual async Task SetManyAsync( + IEnumerable> items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null, + CancellationToken token = default) + { + var itemsArray = items.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) + { + await SetManyFallbackAsync( + itemsArray, + options, + hideErrors, + token + ); + + return; + } + + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await cacheSupportsMultipleItems.SetManyAsync( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } + } + + protected virtual async Task SetManyFallbackAsync( + KeyValuePair[] items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null, + CancellationToken token = default) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + foreach (var item in items) + { + await SetAsync( + item.Key, + item.Value, + options: options, + hideErrors: false, + token: token + ); + } + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } + } + public virtual void Refresh( TCacheKey key, bool? - hideErrors = null) + hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -373,20 +650,14 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + HandleException(ex); return; } throw; } } - /// - /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The for the task. - /// The indicating that the operation is asynchronous. + public virtual async Task RefreshAsync( TCacheKey key, bool? hideErrors = null, @@ -410,11 +681,6 @@ namespace Volo.Abp.Caching } } - /// - /// Removes the cache item for given key from cache. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( TCacheKey key, bool? hideErrors = null) @@ -429,7 +695,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + HandleException(ex); return; } @@ -437,13 +703,6 @@ namespace Volo.Abp.Caching } } - /// - /// Removes the cache item for given key from cache. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The for the task. - /// The indicating that the operation is asynchronous. public virtual async Task RemoveAsync( TCacheKey key, bool? hideErrors = null, @@ -467,6 +726,11 @@ namespace Volo.Abp.Caching } } + protected virtual void HandleException(Exception ex) + { + AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + } + protected virtual async Task HandleExceptionAsync(Exception ex) { Logger.LogException(ex, LogLevel.Warning); @@ -478,5 +742,56 @@ namespace Volo.Abp.Caching .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning)); } } + + protected virtual KeyValuePair[] ToCacheItems(byte[][] itemBytes, TCacheKey[] itemKeys) + { + if (itemBytes.Length != itemKeys.Length) + { + throw new AbpException("count of the item bytes should be same with the count of the given keys"); + } + + var result = new List>(); + + for (int i = 0; i < itemKeys.Length; i++) + { + result.Add( + new KeyValuePair( + itemKeys[i], + ToCacheItem(itemBytes[i]) + ) + ); + } + + return result.ToArray(); + } + + [CanBeNull] + protected virtual TCacheItem ToCacheItem([CanBeNull] byte[] bytes) + { + if (bytes == null) + { + return null; + } + + return Serializer.Deserialize(bytes); + } + + + protected virtual KeyValuePair[] ToRawCacheItems(KeyValuePair[] items) + { + return items + .Select(i => new KeyValuePair( + NormalizeKey(i.Key), + Serializer.Serialize(i.Value) + ) + ).ToArray(); + } + + private static KeyValuePair[] ToCacheItemsWithDefaultValues(TCacheKey[] keys) + { + return keys + .Select(key => new KeyValuePair(key, default)) + .ToArray(); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs new file mode 100644 index 0000000000..af6f87021b --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; + +namespace Volo.Abp.Caching +{ + public interface ICacheSupportsMultipleItems + { + byte[][] GetMany( + IEnumerable keys + ); + + Task GetManyAsync( + IEnumerable keys, + CancellationToken token = default + ); + + void SetMany( + IEnumerable> items, + DistributedCacheEntryOptions options + ); + + Task SetManyAsync( + IEnumerable> items, + DistributedCacheEntryOptions options, + CancellationToken token = default + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs index 163da05632..80fe124785 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -34,6 +35,39 @@ namespace Volo.Abp.Caching TCacheKey key, bool? hideErrors = null ); + + /// + /// Gets multiple cache items with the given keys. + /// + /// The returned list contains exactly the same count of items specified in the given keys. + /// An item in the return list can not be null, but an item in the list has null value + /// if the related key not found in the cache. + /// + /// The keys of cached items to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// List of cache items. + KeyValuePair[] GetMany( + IEnumerable keys, + bool? hideErrors = null + ); + + /// + /// Gets multiple cache items with the given keys. + /// + /// The returned list contains exactly the same count of items specified in the given keys. + /// An item in the return list can not be null, but an item in the list has null value + /// if the related key not found in the cache. + /// + /// + /// The keys of cached items to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// /// The for the task. + /// List of cache items. + Task[]> GetManyAsync( + IEnumerable keys, + bool? hideErrors = null, + CancellationToken token = default + ); /// /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. @@ -113,6 +147,35 @@ namespace Volo.Abp.Caching CancellationToken token = default ); + /// + /// Sets multiple cache items. + /// Based on the implementation, this can be more efficient than setting multiple items individually. + /// + /// Items to set on the cache + /// The cache options for the value. + /// Indicates to throw or hide the exceptions for the distributed cache. + void SetMany( + IEnumerable> items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null + ); + + /// + /// Sets multiple cache items. + /// Based on the implementation, this can be more efficient than setting multiple items individually. + /// + /// Items to set on the cache + /// The cache options for the value. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. + Task SetManyAsync( + IEnumerable> items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null, + CancellationToken token = default + ); + /// /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. /// diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs index 2b524d9272..b11e3a54f4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs @@ -51,17 +51,17 @@ namespace Volo.Abp.Cli.Commands var apiUrl = commandLineArgs.Options.GetOrNull(Options.ApiUrl.Short, Options.ApiUrl.Long); if (string.IsNullOrWhiteSpace(apiUrl)) { - var environmentJson = File.ReadAllText("src/environments/environment.ts").Split("export const environment = ")[1].Replace(";", " "); + var environmentJson = File.ReadAllText("projects/dev-app/src/environments/environment.ts").Split("export const environment = ")[1].Replace(";", " "); var environment = JObject.Parse(environmentJson); apiUrl = environment["apis"]["default"]["url"].ToString(); } - apiUrl += "/api/abp/api-definition?IncludeTypes=true"; + apiUrl = apiUrl.EnsureEndsWith('/') + "api/abp/api-definition?IncludeTypes=true"; var uiFramework = GetUiFramework(commandLineArgs); output = commandLineArgs.Options.GetOrNull(Options.Output.Short, Options.Output.Long); if (!string.IsNullOrWhiteSpace(output) && !(output.EndsWith("/") || output.EndsWith("\\"))) - { + { output += "/"; } @@ -110,7 +110,7 @@ namespace Volo.Abp.Cli.Commands if (rootPath != "app") { Logger.LogInformation($"{rootPath} directory is creating"); - } + } if (rootPath == "app") { @@ -469,17 +469,21 @@ namespace Volo.Abp.Cli.Commands if (returnValueType.Contains("<")) { returnValueType = returnValueType.Split('<')[1].Split('>')[0]; + var clrType = Type.GetType(returnValueType, throwOnError: false); + if (clrType != null && TypeHelper.IsPrimitiveExtended(clrType, includeEnums: true)) + { + return null; + } + if (returnValueType.StartsWith("Volo.Abp.Application.Dtos") - || returnValueType.StartsWith("System.Collections") - || returnValueType == "System.String" - || returnValueType == "System.Void" - || returnValueType.Contains("System.Net.HttpStatusCode?") - || returnValueType.Contains("IActionResult") - || returnValueType.Contains("ActionResult") - || returnValueType.Contains("IStringValueType") - || returnValueType.Contains("IValueValidator") - || returnValueType.Contains("Guid") - ) + || returnValueType.StartsWith("System.Collections") + || returnValueType == "System.Void" + || returnValueType.Contains("System.Net.HttpStatusCode?") + || returnValueType.Contains("IActionResult") + || returnValueType.Contains("ActionResult") + || returnValueType.Contains("IStringValueType") + || returnValueType.Contains("IValueValidator") + ) { return null; } @@ -536,7 +540,7 @@ namespace Volo.Abp.Cli.Commands modelFileText.AppendLine(Environment.NewLine); modelFileText.AppendLine($"import {{ {baseTypeName} }} from '{baseTypeKebabCase}';"); - + extends = "extends " + (!string.IsNullOrWhiteSpace(customBaseTypeName) ? customBaseTypeName : baseTypeName); var modelIndex = CreateType(data, baseType, rootPath, modelIndexList, controllerPathName); @@ -628,7 +632,7 @@ namespace Volo.Abp.Cli.Commands { from = "./" + propertyTypeKebabCase; } - + modelFileText.Insert(0, $"import {{ {propertyType} }} from '{from}';"); modelFileText.Insert(0, Environment.NewLine); modelIndexList.Add(modelIndex); @@ -816,4 +820,4 @@ namespace Volo.Abp.Cli.Commands public bool IsOptional { get; set; } public string BindingSourceId { get; set; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs index b4a4ff8bdd..fbe7fa675f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs @@ -59,15 +59,33 @@ namespace Volo.Abp.Cli.Commands return; } - var result = CmdHelper.RunCmd("dotnet tool install " + SuitePackageName + " --add-source " + nugetIndexUrl + " -g"); + try + { + var result = CmdHelper.RunCmd("dotnet tool install " + SuitePackageName + " --add-source " + nugetIndexUrl + " -g"); - if (result == 0) + if (result == 0) + { + Logger.LogInformation("ABP Suite has been successfully installed."); + Logger.LogInformation("You can run it with the CLI command \"abp suite\""); + } + else + { + ShowSuiteManualInstallCommand(); + } + } + catch (Exception e) { - Logger.LogInformation("ABP Suite has been successfully installed."); - Logger.LogInformation("You can run it with the CLI command \"abp suite\""); + Logger.LogError("Couldn't install ABP Suite." + e.Message); + ShowSuiteManualInstallCommand(); } } + private void ShowSuiteManualInstallCommand() + { + Logger.LogInformation("You can also run the following command to install ABP Suite."); + Logger.LogInformation("dotnet tool install -g Volo.Abp.Suite"); + } + private async Task UpdateSuiteAsync() { var nugetIndexUrl = await _nuGetIndexUrlService.GetAsync(); @@ -77,7 +95,26 @@ namespace Volo.Abp.Cli.Commands return; } - CmdHelper.RunCmd("dotnet tool update " + SuitePackageName + " --add-source " + nugetIndexUrl + " -g"); + try + { + var result = CmdHelper.RunCmd("dotnet tool update " + SuitePackageName + " --add-source " + nugetIndexUrl + " -g"); + + if (result != 0) + { + ShowSuiteManualUpdateCommand(); + } + } + catch (Exception ex) + { + Logger.LogError("Couldn't update ABP Suite." + ex.Message); + ShowSuiteManualUpdateCommand(); + } + } + + private void ShowSuiteManualUpdateCommand() + { + Logger.LogError("You can also run the following command to update ABP Suite."); + Logger.LogError("dotnet tool update -g Volo.Abp.Suite"); } private static void RemoveSuite() diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs index 9a76f9ee21..b69ce479ac 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs @@ -10,9 +10,9 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App { var fileEntries = context.Files.Where(x => !x.IsDirectory && - (x.Name.EndsWith("angular/src/environments/environment.ts", StringComparison.InvariantCultureIgnoreCase) || - x.Name.EndsWith("angular/src/environments/environment.hmr.ts", StringComparison.InvariantCultureIgnoreCase) || - x.Name.EndsWith("angular/src/environments/environment.prod.ts", StringComparison.InvariantCultureIgnoreCase)) + (x.Name.EndsWith("angular/projects/dev-app/src/environments/environment.ts", StringComparison.InvariantCultureIgnoreCase) || + x.Name.EndsWith("angular/projects/dev-app/src/environments/environment.hmr.ts", StringComparison.InvariantCultureIgnoreCase) || + x.Name.EndsWith("angular/projects/dev-app/src/environments/environment.prod.ts", StringComparison.InvariantCultureIgnoreCase)) ) .ToList(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs index 02ae0040bc..be5b553744 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs @@ -54,11 +54,12 @@ namespace Volo.Abp.Cli.ProjectModification _npmGlobalPackagesChecker.Check(); var packagesUpdated = new ConcurrentDictionary(); + async Task UpdateAsync(string file) { var updated = await UpdatePackagesInFile(file, includePreviews, switchToStable); packagesUpdated.TryAdd(file, updated); - }; + } Task.WaitAll(fileList.Select(UpdateAsync).ToArray()); @@ -66,23 +67,20 @@ namespace Volo.Abp.Cli.ProjectModification { var fileDirectory = Path.GetDirectoryName(file.Key).EnsureEndsWith(Path.DirectorySeparatorChar); - if (IsAngularProject(fileDirectory)) + if (includePreviews) { - if (includePreviews) - { - await CreateNpmrcFileAsync(Path.GetDirectoryName(file.Key)); - } - else if (switchToStable) - { - await DeleteNpmrcFileAsync(Path.GetDirectoryName(file.Key)); - } + await CreateNpmrcFileAsync(Path.GetDirectoryName(file.Key)); + RunNpmInstall(fileDirectory); + } + else if (switchToStable) + { + await DeleteNpmrcFileAsync(Path.GetDirectoryName(file.Key)); + RunYarn(fileDirectory); } - - RunYarn(fileDirectory); if (!IsAngularProject(fileDirectory)) { - Thread.Sleep(500); + Thread.Sleep(1000); RunGulp(fileDirectory); } } @@ -166,7 +164,8 @@ namespace Volo.Abp.Cli.ProjectModification return File.Exists(Path.Combine(fileDirectory, "angular.json")); } - protected virtual async Task UpdatePackagesInFile(string filePath, bool includePreviews = false, bool switchToStable = false) + protected virtual async Task UpdatePackagesInFile(string filePath, bool includePreviews = false, + bool switchToStable = false) { var packagesUpdated = false; var fileContent = File.ReadAllText(filePath); @@ -201,67 +200,54 @@ namespace Volo.Abp.Cli.ProjectModification bool includePreviews = false, bool switchToStable = false) { - var currentVersion = (string)package.Value; + var currentVersion = (string) package.Value; - var version = await GetLatestVersion(package, currentVersion, includePreviews, switchToStable); - - var versionWithPrefix = $"~{version}"; + var version = ""; + if (includePreviews || (!switchToStable && currentVersion.Contains("-preview"))) + { + version = "preview"; + } + else + { + version = await GetLatestVersion(package); + } - if (versionWithPrefix == currentVersion) + if (version == currentVersion) { return false; } - package.Value.Replace(versionWithPrefix); + package.Value.Replace(version); - Logger.LogInformation($"Updated {package.Name} to {version} in {filePath.Replace(Directory.GetCurrentDirectory(), "")}."); + Logger.LogInformation( + $"Updated {package.Name} to {version} in {filePath.Replace(Directory.GetCurrentDirectory(), "")}."); return true; } protected virtual async Task GetLatestVersion( - JProperty package, - string currentVersion, - bool includePreviews = false, - bool switchToStable = false) + JProperty package) { if (_fileVersionStorage.ContainsKey(package.Name)) { return _fileVersionStorage[package.Name]; } - var newVersion = currentVersion; + var newVersion = CmdHelper.RunCmdAndGetOutput($"npm show {package.Name} version"); + var newVersionWithPrefix = $"~{newVersion}"; - if (includePreviews || (!switchToStable && currentVersion.Contains("-preview"))) - { - if (_myGetApiResponse == null) - { - _myGetApiResponse = await _myGetPackageListFinder.GetPackagesAsync(); - } + _fileVersionStorage[package.Name] = newVersionWithPrefix; - var mygetPackage = _myGetApiResponse.Packages.FirstOrDefault(p => p.Id == package.Name); - if (mygetPackage != null) - { - newVersion = mygetPackage.Versions.Last(); - } - } - else - { - newVersion = CmdHelper.RunCmdAndGetOutput($"npm show {package.Name} version"); - } - - _fileVersionStorage[package.Name] = newVersion; - - return newVersion; + return $"~{newVersionWithPrefix}"; } protected virtual List GetAbpPackagesFromPackageJson(JObject fileObject) { - var dependencyList = new[] { "dependencies", "devDependencies", "peerDependencies" }; + var dependencyList = new[] {"dependencies", "devDependencies", "peerDependencies"}; var abpPackages = new List(); foreach (var dependencyListName in dependencyList) { - var dependencies = (JObject)fileObject[dependencyListName]; + var dependencies = (JObject) fileObject[dependencyListName]; if (dependencies == null) { @@ -269,7 +255,8 @@ namespace Volo.Abp.Cli.ProjectModification } var properties = dependencies.Properties().ToList(); - abpPackages.AddRange(properties.Where(p => p.Name.StartsWith("@abp/") || p.Name.StartsWith("@volo/")).ToList()); + abpPackages.AddRange(properties.Where(p => p.Name.StartsWith("@abp/") || p.Name.StartsWith("@volo/")) + .ToList()); } return abpPackages; @@ -286,5 +273,11 @@ namespace Volo.Abp.Cli.ProjectModification Logger.LogInformation($"Running Yarn on {fileDirectory}"); CmdHelper.RunCmd($"cd {fileDirectory} && yarn"); } + + protected virtual void RunNpmInstall(string fileDirectory) + { + Logger.LogInformation($"Running npm install on {fileDirectory}"); + CmdHelper.RunCmd($"cd {fileDirectory} && npm install"); + } } } diff --git a/framework/src/Volo.Abp.Cli/Properties/launchSettings.json b/framework/src/Volo.Abp.Cli/Properties/launchSettings.json deleted file mode 100644 index e7f99eea04..0000000000 --- a/framework/src/Volo.Abp.Cli/Properties/launchSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "profiles": { - "Volo.Abp.Cli": { - "commandName": "Project" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/System/Runtime/IOSPlatformProvider.cs b/framework/src/Volo.Abp.Core/System/Runtime/IOSPlatformProvider.cs new file mode 100644 index 0000000000..60240159f3 --- /dev/null +++ b/framework/src/Volo.Abp.Core/System/Runtime/IOSPlatformProvider.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace System.Runtime +{ + public interface IOSPlatformProvider + { + OSPlatform GetCurrentOSPlatform(); + } +} diff --git a/framework/src/Volo.Abp.Core/System/Runtime/OSPlatformProvider.cs b/framework/src/Volo.Abp.Core/System/Runtime/OSPlatformProvider.cs new file mode 100644 index 0000000000..89b57acaab --- /dev/null +++ b/framework/src/Volo.Abp.Core/System/Runtime/OSPlatformProvider.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; +using Volo.Abp.DependencyInjection; + +namespace System.Runtime +{ + public class OSPlatformProvider : IOSPlatformProvider, ITransientDependency + { + public virtual OSPlatform GetCurrentOSPlatform() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OSPlatform.OSX; //MAC + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OSPlatform.Windows; + } + + return OSPlatform.Linux; + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Collections/ITypeList.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Collections/ITypeList.cs index 6d53ddc954..8906859edf 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Collections/ITypeList.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Collections/ITypeList.cs @@ -27,7 +27,7 @@ namespace Volo.Abp.Collections /// Adds a type to list if it's not already in the list. /// /// Type - void TryAdd() where T : TBaseType; + bool TryAdd() where T : TBaseType; /// /// Checks if a type exists in the list. diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Collections/TypeList.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Collections/TypeList.cs index 24e66085f7..1f4b7ddc40 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Collections/TypeList.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Collections/TypeList.cs @@ -60,14 +60,15 @@ namespace Volo.Abp.Collections _typeList.Add(typeof(T)); } - public void TryAdd() where T : TBaseType + public bool TryAdd() where T : TBaseType { if (Contains()) { - return; + return false; } Add(); + return true; } /// diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicMaxLengthAttribute.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicMaxLengthAttribute.cs new file mode 100644 index 0000000000..59d5bcfa47 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicMaxLengthAttribute.cs @@ -0,0 +1,39 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Reflection; +using JetBrains.Annotations; + +namespace Volo.Abp.Validation +{ + public class DynamicMaxLengthAttribute : MaxLengthAttribute + { + private static readonly FieldInfo MaximumLengthField; + + static DynamicMaxLengthAttribute() + { + MaximumLengthField = typeof(MaxLengthAttribute).GetField( + "k__BackingField", + BindingFlags.Instance | BindingFlags.NonPublic + ); + Debug.Assert(MaximumLengthField != null, nameof(MaximumLengthField) + " != null"); + } + + public DynamicMaxLengthAttribute( + [NotNull] Type sourceType, + [CanBeNull] string maximumLengthPropertyName) + { + Check.NotNull(sourceType, nameof(sourceType)); + + if (maximumLengthPropertyName != null) + { + var maximumLengthProperty = sourceType.GetProperty( + maximumLengthPropertyName, + BindingFlags.Static | BindingFlags.Public + ); + Debug.Assert(maximumLengthProperty != null, nameof(maximumLengthProperty) + " != null"); + MaximumLengthField.SetValue(this, (int) maximumLengthProperty.GetValue(null)); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicStringLengthAttribute.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicStringLengthAttribute.cs new file mode 100644 index 0000000000..d27a04b1ba --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicStringLengthAttribute.cs @@ -0,0 +1,58 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Reflection; +using JetBrains.Annotations; + +namespace Volo.Abp.Validation +{ + /// + /// Used to determine and + /// properties on the runtime. + /// + public class DynamicStringLengthAttribute : StringLengthAttribute + { + private static readonly FieldInfo MaximumLengthField; + + static DynamicStringLengthAttribute() + { + MaximumLengthField = typeof(StringLengthAttribute).GetField( + "k__BackingField", + BindingFlags.Instance | BindingFlags.NonPublic + ); + Debug.Assert(MaximumLengthField != null, nameof(MaximumLengthField) + " != null"); + } + + /// A type to get the values of the properties + /// The name of the public static property for the + /// The name of the public static property for the + public DynamicStringLengthAttribute( + [NotNull] Type sourceType, + [CanBeNull] string maximumLengthPropertyName, + [CanBeNull] string minimumLengthPropertyName = null) + : base(0) + { + Check.NotNull(sourceType, nameof(sourceType)); + + if (maximumLengthPropertyName != null) + { + var maximumLengthProperty = sourceType.GetProperty( + maximumLengthPropertyName, + BindingFlags.Static | BindingFlags.Public + ); + Debug.Assert(maximumLengthProperty != null, nameof(maximumLengthProperty) + " != null"); + MaximumLengthField.SetValue(this, (int) maximumLengthProperty.GetValue(null)); + } + + if (minimumLengthPropertyName != null) + { + var minimumLengthProperty = sourceType.GetProperty( + minimumLengthPropertyName, + BindingFlags.Static | BindingFlags.Public + ); + Debug.Assert(minimumLengthProperty != null, nameof(minimumLengthProperty) + " != null"); + MinimumLength = (int) minimumLengthProperty.GetValue(null); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICreateAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICreateAppService.cs new file mode 100644 index 0000000000..a5d75b0b4b --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICreateAppService.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Application.Services +{ + public interface ICreateAppService + : ICreateAppService + { + + } + + public interface ICreateAppService + : IApplicationService + { + Task CreateAsync(TCreateInput input); + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICreateUpdateAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICreateUpdateAppService.cs new file mode 100644 index 0000000000..ce3698405c --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICreateUpdateAppService.cs @@ -0,0 +1,21 @@ +namespace Volo.Abp.Application.Services +{ + public interface ICreateUpdateAppService + : ICreateUpdateAppService + { + + } + + public interface ICreateUpdateAppService + : ICreateUpdateAppService + { + + } + + public interface ICreateUpdateAppService + : ICreateAppService, + IUpdateAppService + { + + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs index 12f3b51d11..10f88db22a 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using Volo.Abp.Application.Dtos; namespace Volo.Abp.Application.Services @@ -28,16 +27,10 @@ namespace Volo.Abp.Application.Services } public interface ICrudAppService - : IApplicationService + : IReadOnlyAppService, + ICreateUpdateAppService, + IDeleteAppService { - Task GetAsync(TKey id); - Task> GetListAsync(TGetListInput input); - - Task CreateAsync(TCreateInput input); - - Task UpdateAsync(TKey id, TUpdateInput input); - - Task DeleteAsync(TKey id); } } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IDeleteAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IDeleteAppService.cs new file mode 100644 index 0000000000..50badcead9 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IDeleteAppService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Application.Services +{ + public interface IDeleteAppService : IApplicationService + { + Task DeleteAsync(TKey id); + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IReadOnlyAppService.cs new file mode 100644 index 0000000000..09df0dc9cd --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IReadOnlyAppService.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.Application.Services +{ + public interface IReadOnlyAppService + : IReadOnlyAppService + { + + } + + public interface IReadOnlyAppService + : IReadOnlyAppService + { + + } + + public interface IReadOnlyAppService + : IApplicationService + { + Task GetAsync(TKey id); + + Task> GetListAsync(TGetListInput input); + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IUpdateAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IUpdateAppService.cs new file mode 100644 index 0000000000..c0904709bf --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IUpdateAppService.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Application.Services +{ + public interface IUpdateAppService + : IUpdateAppService + { + + } + + public interface IUpdateAppService + : IApplicationService + { + Task UpdateAsync(TKey id, TUpdateInput input); + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs index e6f433cad6..11550c456f 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs @@ -1,9 +1,6 @@ using System; -using System.Linq; -using System.Linq.Dynamic.Core; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; -using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; using Volo.Abp.MultiTenancy; @@ -61,17 +58,12 @@ namespace Volo.Abp.Application.Services } public abstract class AbstractKeyCrudAppService - : ApplicationService, + : AbstractKeyReadOnlyAppService, ICrudAppService where TEntity : class, IEntity { - protected IRepository Repository { get; } - protected virtual string GetPolicyName { get; set; } - - protected virtual string GetListPolicyName { get; set; } - protected virtual string CreatePolicyName { get; set; } protected virtual string UpdatePolicyName { get; set; } @@ -79,37 +71,11 @@ namespace Volo.Abp.Application.Services protected virtual string DeletePolicyName { get; set; } protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) { Repository = repository; } - public virtual async Task GetAsync(TKey id) - { - await CheckGetPolicyAsync(); - - var entity = await GetEntityByIdAsync(id); - return MapToGetOutputDto(entity); - } - - public virtual async Task> GetListAsync(TGetListInput input) - { - await CheckGetListPolicyAsync(); - - var query = CreateFilteredQuery(input); - - var totalCount = await AsyncExecuter.CountAsync(query); - - query = ApplySorting(query, input); - query = ApplyPaging(query, input); - - var entities = await AsyncExecuter.ToListAsync(query); - - return new PagedResultDto( - totalCount, - entities.Select(MapToGetListOutputDto).ToList() - ); - } - public virtual async Task CreateAsync(TCreateInput input) { await CheckCreatePolicyAsync(); @@ -144,18 +110,6 @@ namespace Volo.Abp.Application.Services protected abstract Task DeleteByIdAsync(TKey id); - protected abstract Task GetEntityByIdAsync(TKey id); - - protected virtual async Task CheckGetPolicyAsync() - { - await CheckPolicyAsync(GetPolicyName); - } - - protected virtual async Task CheckGetListPolicyAsync() - { - await CheckPolicyAsync(GetListPolicyName); - } - protected virtual async Task CheckCreatePolicyAsync() { await CheckPolicyAsync(CreatePolicyName); @@ -171,101 +125,6 @@ namespace Volo.Abp.Application.Services await CheckPolicyAsync(DeletePolicyName); } - /// - /// Should apply sorting if needed. - /// - /// The query. - /// The input. - protected virtual IQueryable ApplySorting(IQueryable query, TGetListInput input) - { - //Try to sort query if available - if (input is ISortedResultRequest sortInput) - { - if (!sortInput.Sorting.IsNullOrWhiteSpace()) - { - return query.OrderBy(sortInput.Sorting); - } - } - - //IQueryable.Task requires sorting, so we should sort if Take will be used. - if (input is ILimitedResultRequest) - { - return ApplyDefaultSorting(query); - } - - //No sorting - return query; - } - - /// - /// Applies sorting if no sorting specified but a limited result requested. - /// - /// The query. - protected virtual IQueryable ApplyDefaultSorting(IQueryable query) - { - if (typeof(TEntity).IsAssignableTo()) - { - return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); - } - - throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyCrudAppService!"); - } - - /// - /// Should apply paging if needed. - /// - /// The query. - /// The input. - protected virtual IQueryable ApplyPaging(IQueryable query, TGetListInput input) - { - //Try to use paging if available - if (input is IPagedResultRequest pagedInput) - { - return query.PageBy(pagedInput); - } - - //Try to limit query result if available - if (input is ILimitedResultRequest limitedInput) - { - return query.Take(limitedInput.MaxResultCount); - } - - //No paging - return query; - } - - /// - /// This method should create based on given input. - /// It should filter query if needed, but should not do sorting or paging. - /// Sorting should be done in and paging should be done in - /// methods. - /// - /// The input. - protected virtual IQueryable CreateFilteredQuery(TGetListInput input) - { - return Repository; - } - - /// - /// Maps to . - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity) - { - return ObjectMapper.Map(entity); - } - - /// - /// Maps to . - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity) - { - return ObjectMapper.Map(entity); - } - /// /// Maps to to create a new entity. /// It uses by default. @@ -284,18 +143,14 @@ namespace Volo.Abp.Application.Services /// protected virtual void SetIdForGuids(TEntity entity) { - var entityWithGuidId = entity as IEntity; - - if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty) + if (entity is IEntity entityWithGuidId && entityWithGuidId.Id == Guid.Empty) { - return; + EntityHelper.TrySetId( + entityWithGuidId, + () => GuidGenerator.Create(), + true + ); } - - EntityHelper.TrySetId( - entityWithGuidId, - () => GuidGenerator.Create(), - true - ); } /// diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs new file mode 100644 index 0000000000..9085434211 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs @@ -0,0 +1,184 @@ +using System; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.Application.Services +{ + public abstract class AbstractKeyReadOnlyAppService + : AbstractKeyReadOnlyAppService + where TEntity : class, IEntity + { + protected AbstractKeyReadOnlyAppService(IReadOnlyRepository repository) + : base(repository) + { + + } + } + + public abstract class AbstractKeyReadOnlyAppService + : AbstractKeyReadOnlyAppService + where TEntity : class, IEntity + { + protected AbstractKeyReadOnlyAppService(IReadOnlyRepository repository) + : base(repository) + { + + } + } + + public abstract class AbstractKeyReadOnlyAppService + : ApplicationService + , IReadOnlyAppService + where TEntity : class, IEntity + { + protected IReadOnlyRepository ReadOnlyRepository { get; } + + protected virtual string GetPolicyName { get; set; } + + protected virtual string GetListPolicyName { get; set; } + + protected AbstractKeyReadOnlyAppService(IReadOnlyRepository repository) + { + ReadOnlyRepository = repository; + } + + public virtual async Task GetAsync(TKey id) + { + await CheckGetPolicyAsync(); + + var entity = await GetEntityByIdAsync(id); + return MapToGetOutputDto(entity); + } + + public virtual async Task> GetListAsync(TGetListInput input) + { + await CheckGetListPolicyAsync(); + + var query = CreateFilteredQuery(input); + + var totalCount = await AsyncExecuter.CountAsync(query); + + query = ApplySorting(query, input); + query = ApplyPaging(query, input); + + var entities = await AsyncExecuter.ToListAsync(query); + + return new PagedResultDto( + totalCount, + entities.Select(MapToGetListOutputDto).ToList() + ); + } + + protected abstract Task GetEntityByIdAsync(TKey id); + + protected virtual async Task CheckGetPolicyAsync() + { + await CheckPolicyAsync(GetPolicyName); + } + + protected virtual async Task CheckGetListPolicyAsync() + { + await CheckPolicyAsync(GetListPolicyName); + } + + /// + /// Should apply sorting if needed. + /// + /// The query. + /// The input. + protected virtual IQueryable ApplySorting(IQueryable query, TGetListInput input) + { + //Try to sort query if available + if (input is ISortedResultRequest sortInput) + { + if (!sortInput.Sorting.IsNullOrWhiteSpace()) + { + return query.OrderBy(sortInput.Sorting); + } + } + + //IQueryable.Task requires sorting, so we should sort if Take will be used. + if (input is ILimitedResultRequest) + { + return ApplyDefaultSorting(query); + } + + //No sorting + return query; + } + + /// + /// Applies sorting if no sorting specified but a limited result requested. + /// + /// The query. + protected virtual IQueryable ApplyDefaultSorting(IQueryable query) + { + if (typeof(TEntity).IsAssignableTo()) + { + return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + } + + throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!"); + } + + /// + /// Should apply paging if needed. + /// + /// The query. + /// The input. + protected virtual IQueryable ApplyPaging(IQueryable query, TGetListInput input) + { + //Try to use paging if available + if (input is IPagedResultRequest pagedInput) + { + return query.PageBy(pagedInput); + } + + //Try to limit query result if available + if (input is ILimitedResultRequest limitedInput) + { + return query.Take(limitedInput.MaxResultCount); + } + + //No paging + return query; + } + + /// + /// This method should create based on given input. + /// It should filter query if needed, but should not do sorting or paging. + /// Sorting should be done in and paging should be done in + /// methods. + /// + /// The input. + protected virtual IQueryable CreateFilteredQuery(TGetListInput input) + { + return ReadOnlyRepository; + } + + /// + /// Maps to . + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity) + { + return ObjectMapper.Map(entity); + } + + /// + /// Maps to . + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity) + { + return ObjectMapper.Map(entity); + } + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs index a2af0592a4..7832e8a4ab 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs @@ -70,7 +70,7 @@ namespace Volo.Abp.Application.Services protected new IRepository Repository { get; } protected CrudAppService(IRepository repository) - : base(repository) + : base(repository) { Repository = repository; } diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs new file mode 100644 index 0000000000..d99928126c --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.Application.Services +{ + public abstract class ReadOnlyAppService + : ReadOnlyAppService + where TEntity : class, IEntity + where TEntityDto : IEntityDto + { + protected ReadOnlyAppService(IReadOnlyRepository repository) + : base(repository) + { + + } + } + + public abstract class ReadOnlyAppService + : ReadOnlyAppService + where TEntity : class, IEntity + where TEntityDto : IEntityDto + { + protected ReadOnlyAppService(IReadOnlyRepository repository) + : base(repository) + { + + } + } + + public abstract class ReadOnlyAppService + : AbstractKeyReadOnlyAppService + where TEntity : class, IEntity + where TGetOutputDto : IEntityDto + where TGetListOutputDto : IEntityDto + { + protected new IReadOnlyRepository Repository { get; } + + protected ReadOnlyAppService(IReadOnlyRepository repository) + : base(repository) + { + Repository = repository; + } + + protected override async Task GetEntityByIdAsync(TKey id) + { + return await Repository.GetAsync(id); + } + + protected override IQueryable ApplyDefaultSorting(IQueryable query) + { + if (typeof(TEntity).IsAssignableTo()) + { + return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + } + else + { + return query.OrderByDescending(e => e.Id); + } + } + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/ConcurrencyStampConsts.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/ConcurrencyStampConsts.cs new file mode 100644 index 0000000000..ac655a395d --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/ConcurrencyStampConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Domain.Entities +{ + public static class ConcurrencyStampConsts + { + public const int MaxLength = 40; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index 8db091eb30..796c6e20fb 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -241,6 +242,11 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory } } + if (IsBaseAuditProperty(propertyInfo, entityType)) + { + return false; + } + var isModified = !(propertyEntry.OriginalValue?.Equals(propertyEntry.CurrentValue) ?? propertyEntry.CurrentValue == null); if (isModified) { @@ -250,6 +256,59 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory return defaultValue; } + private bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType) + { + if (entityType.IsAssignableTo() + && propertyInfo.Name == nameof(IHasCreationTime.CreationTime)) + { + return true; + } + + if (entityType.IsAssignableTo() + && propertyInfo.Name == nameof(IMayHaveCreator.CreatorId)) + { + return true; + } + + if (entityType.IsAssignableTo() + && propertyInfo.Name == nameof(IMustHaveCreator.CreatorId)) + { + return true; + } + + if (entityType.IsAssignableTo() + && propertyInfo.Name == nameof(IHasModificationTime.LastModificationTime)) + { + return true; + } + + if (entityType.IsAssignableTo() + && propertyInfo.Name == nameof(IModificationAuditedObject.LastModifierId)) + { + return true; + } + + if (entityType.IsAssignableTo() + && propertyInfo.Name == nameof(ISoftDelete.IsDeleted)) + { + return true; + } + + if (entityType.IsAssignableTo() + && propertyInfo.Name == nameof(IHasDeletionTime.DeletionTime)) + { + return true; + } + + if (entityType.IsAssignableTo() + && propertyInfo.Name == nameof(IDeletionAuditedObject.DeleterId)) + { + return true; + } + + return false; + } + /// /// Updates change time, entity id and foreign keys after SaveChanges is called. /// @@ -313,4 +372,4 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory } } } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs index 0a84b6871d..44962eca4c 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs @@ -40,9 +40,9 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling { if (b.Metadata.ClrType.IsAssignableTo()) { - //TODO: Max length? b.Property(nameof(IHasConcurrencyStamp.ConcurrencyStamp)) .IsConcurrencyToken() + .HasMaxLength(ConcurrencyStampConsts.MaxLength) .HasColumnName(nameof(IHasConcurrencyStamp.ConcurrencyStamp)); } } diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json b/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json deleted file mode 100644 index d56e5c65b7..0000000000 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:52306/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Http.Client.IdentityModel": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:52307/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Properties/launchSettings.json b/framework/src/Volo.Abp.Http.Client.IdentityModel/Properties/launchSettings.json deleted file mode 100644 index d56e5c65b7..0000000000 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:52306/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Http.Client.IdentityModel": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:52307/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Properties/launchSettings.json b/framework/src/Volo.Abp.Http.Client/Properties/launchSettings.json deleted file mode 100644 index 3cef3cf668..0000000000 --- a/framework/src/Volo.Abp.Http.Client/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:52208/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Http.Client": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:52209/" - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepositoryFilterer.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepositoryFilterer.cs new file mode 100644 index 0000000000..1790c8af6b --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepositoryFilterer.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using MongoDB.Driver; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Domain.Repositories.MongoDB +{ + public interface IMongoDbRepositoryFilterer where TEntity : class, IEntity + { + void AddGlobalFilters(List> filters); + } + + public interface IMongoDbRepositoryFilterer : IMongoDbRepositoryFilterer where TEntity : class, IEntity + { + FilterDefinition CreateEntityFilter(TKey id, bool applyFilters = false); + + FilterDefinition CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 3ac6ab5239..e8477f20e9 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -14,7 +14,6 @@ using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Local; using Volo.Abp.Guids; using Volo.Abp.MongoDB; -using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Repositories.MongoDB { @@ -331,6 +330,8 @@ namespace Volo.Abp.Domain.Repositories.MongoDB where TMongoDbContext : IAbpMongoDbContext where TEntity : class, IEntity { + public IMongoDbRepositoryFilterer RepositoryFilterer { get; set; } + public MongoDbRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) { @@ -358,7 +359,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB CancellationToken cancellationToken = default) { return await Collection - .Find(CreateEntityFilter(id, true)) + .Find(RepositoryFilterer.CreateEntityFilter(id, true)) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); } @@ -372,49 +373,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB protected override FilterDefinition CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null) { - if (!withConcurrencyStamp || !(entity is IHasConcurrencyStamp entityWithConcurrencyStamp)) - { - return Builders.Filter.Eq(e => e.Id, entity.Id); - } - - if (concurrencyStamp == null) - { - concurrencyStamp = entityWithConcurrencyStamp.ConcurrencyStamp; - } - - return Builders.Filter.And( - Builders.Filter.Eq(e => e.Id, entity.Id), - Builders.Filter.Eq(e => ((IHasConcurrencyStamp)e).ConcurrencyStamp, concurrencyStamp) - ); - } - - protected virtual FilterDefinition CreateEntityFilter(TKey id, bool applyFilters = false) - { - var filters = new List> - { - Builders.Filter.Eq(e => e.Id, id) - }; - - if (applyFilters) - { - AddGlobalFilters(filters); - } - - return Builders.Filter.And(filters); - } - - protected virtual void AddGlobalFilters(List> filters) - { - if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)) && DataFilter.IsEnabled()) - { - filters.Add(Builders.Filter.Eq(e => ((ISoftDelete)e).IsDeleted, false)); - } - - if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity))) - { - var tenantId = CurrentTenant.Id; - filters.Add(Builders.Filter.Eq(e => ((IMultiTenant)e).TenantId, tenantId)); - } + return RepositoryFilterer.CreateEntityFilter(entity, withConcurrencyStamp, concurrencyStamp); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepositoryFilterer.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepositoryFilterer.cs new file mode 100644 index 0000000000..4c07c747f3 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepositoryFilterer.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using MongoDB.Driver; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Domain.Repositories.MongoDB +{ + public class MongoDbRepositoryFilterer : IMongoDbRepositoryFilterer + where TEntity : class, IEntity + { + protected IDataFilter DataFilter { get; } + + protected ICurrentTenant CurrentTenant { get; } + + public MongoDbRepositoryFilterer(IDataFilter dataFilter, ICurrentTenant currentTenant) + { + DataFilter = dataFilter; + CurrentTenant = currentTenant; + } + + public virtual void AddGlobalFilters(List> filters) + { + if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)) && DataFilter.IsEnabled()) + { + filters.Add(Builders.Filter.Eq(e => ((ISoftDelete) e).IsDeleted, false)); + } + + if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity))) + { + var tenantId = CurrentTenant.Id; + filters.Add(Builders.Filter.Eq(e => ((IMultiTenant) e).TenantId, tenantId)); + } + } + } + + public class MongoDbRepositoryFilterer : MongoDbRepositoryFilterer, + IMongoDbRepositoryFilterer + where TEntity : class, IEntity + { + public MongoDbRepositoryFilterer(IDataFilter dataFilter, ICurrentTenant currentTenant) + : base(dataFilter, currentTenant) + { + } + + public FilterDefinition CreateEntityFilter(TKey id, bool applyFilters = false) + { + var filters = new List> + { + Builders.Filter.Eq(e => e.Id, id) + }; + + if (applyFilters) + { + AddGlobalFilters(filters); + } + + return Builders.Filter.And(filters); + } + + public FilterDefinition CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null) + { + if (!withConcurrencyStamp || !(entity is IHasConcurrencyStamp entityWithConcurrencyStamp)) + { + return Builders.Filter.Eq(e => e.Id, entity.Id); + } + + if (concurrencyStamp == null) + { + concurrencyStamp = entityWithConcurrencyStamp.ConcurrencyStamp; + } + + return Builders.Filter.And( + Builders.Filter.Eq(e => e.Id, entity.Id), + Builders.Filter.Eq(e => ((IHasConcurrencyStamp) e).ConcurrencyStamp, concurrencyStamp) + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs index d3e8055a22..e36e972954 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Domain; +using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Modularity; using Volo.Abp.Uow.MongoDB; @@ -14,6 +15,16 @@ namespace Volo.Abp.MongoDB typeof(IMongoDbContextProvider<>), typeof(UnitOfWorkMongoDbContextProvider<>) ); + + context.Services.TryAddTransient( + typeof(IMongoDbRepositoryFilterer<>), + typeof(MongoDbRepositoryFilterer<>) + ); + + context.Services.TryAddTransient( + typeof(IMongoDbRepositoryFilterer<,>), + typeof(MongoDbRepositoryFilterer<,>) + ); } } } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs index 1ba519680c..a637351e66 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.Data { - //TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0 + //TODO: Move to Volo.Abp.Data.ObjectExtending namespace at 4.0? public interface IHasExtraProperties { diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj b/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj index f3a0fefb2a..b4aa55b1c0 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj @@ -17,6 +17,7 @@ + diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/AbpVirtualFileSystemOptions.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/AbpVirtualFileSystemOptions.cs index c4a4d203d2..aff1aee4e9 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/AbpVirtualFileSystemOptions.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/AbpVirtualFileSystemOptions.cs @@ -3,7 +3,7 @@ public class AbpVirtualFileSystemOptions { public VirtualFileSetList FileSets { get; } - + public AbpVirtualFileSystemOptions() { FileSets = new VirtualFileSetList(); diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/EmbeddedFileSet.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/AbpEmbeddedFileProvider.cs similarity index 81% rename from framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/EmbeddedFileSet.cs rename to framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/AbpEmbeddedFileProvider.cs index d2a0556527..ae0acafe09 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/EmbeddedFileSet.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/AbpEmbeddedFileProvider.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.FileProviders; namespace Volo.Abp.VirtualFileSystem.Embedded { - public class EmbeddedFileSet : IVirtualFileSet + public class AbpEmbeddedFileProvider : DictionaryBasedFileProvider { [NotNull] public Assembly Assembly { get; } @@ -16,19 +16,22 @@ namespace Volo.Abp.VirtualFileSystem.Embedded [CanBeNull] public string BaseNamespace { get; } - [CanBeNull] - public string BaseFolderInProject { get; } + protected override IDictionary Files => _files.Value; + private readonly Lazy> _files; - public EmbeddedFileSet( + public AbpEmbeddedFileProvider( [NotNull] Assembly assembly, - [CanBeNull] string baseNamespace = null, - [CanBeNull] string baseFolderInProject = null) + [CanBeNull] string baseNamespace = null) { Check.NotNull(assembly, nameof(assembly)); Assembly = assembly; BaseNamespace = baseNamespace; - BaseFolderInProject = baseFolderInProject; + + _files = new Lazy>( + CreateFiles, + true + ); } public void AddFiles(Dictionary files) @@ -127,5 +130,17 @@ namespace Volo.Abp.VirtualFileSystem.Embedded return filePath.Substring(filePath.LastIndexOf("/", StringComparison.Ordinal) + 1); } + + protected override string NormalizePath(string subpath) + { + return VirtualFilePathHelper.NormalizePath(subpath); + } + + private Dictionary CreateFiles() + { + var files = new Dictionary(StringComparer.OrdinalIgnoreCase); + AddFiles(files); + return files; + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/EmbeddedVirtualFileSetInfo.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/EmbeddedVirtualFileSetInfo.cs new file mode 100644 index 0000000000..9f4d87d783 --- /dev/null +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Embedded/EmbeddedVirtualFileSetInfo.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using Microsoft.Extensions.FileProviders; + +namespace Volo.Abp.VirtualFileSystem.Embedded +{ + public class EmbeddedVirtualFileSetInfo : VirtualFileSetInfo + { + public Assembly Assembly { get; } + + public string BaseFolder { get; } + + public EmbeddedVirtualFileSetInfo( + IFileProvider fileProvider, + Assembly assembly, + string baseFolder = null) + : base(fileProvider) + { + Assembly = assembly; + BaseFolder = baseFolder; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/IVirtualFileSet.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/IVirtualFileSet.cs deleted file mode 100644 index 1880165d02..0000000000 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/IVirtualFileSet.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using Microsoft.Extensions.FileProviders; - -namespace Volo.Abp.VirtualFileSystem -{ - public interface IVirtualFileSet - { - void AddFiles(Dictionary files); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Physical/PhysicalVirtualFileSetInfo.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Physical/PhysicalVirtualFileSetInfo.cs new file mode 100644 index 0000000000..525fa178d6 --- /dev/null +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/Physical/PhysicalVirtualFileSetInfo.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.FileProviders; + +namespace Volo.Abp.VirtualFileSystem.Physical +{ + public class PhysicalVirtualFileSetInfo : VirtualFileSetInfo + { + public string Root { get; } + + public PhysicalVirtualFileSetInfo( + [NotNull] IFileProvider fileProvider, + [NotNull] string root + ) + : base(fileProvider) + { + Root = Check.NotNullOrWhiteSpace(root, nameof(root)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFilePathHelper.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFilePathHelper.cs index 03f851a7e3..47900d5aa9 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFilePathHelper.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFilePathHelper.cs @@ -6,8 +6,6 @@ namespace Volo.Abp.VirtualFileSystem { internal static class VirtualFilePathHelper { - //TODO: Optimize this class! - public static string NormalizePath(string fullPath) { if (fullPath.Equals("/", StringComparison.Ordinal)) diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileProvider.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileProvider.cs index 5f90ef7925..eb4cd4e415 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileProvider.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; @@ -28,6 +27,11 @@ namespace Volo.Abp.VirtualFileSystem public virtual IDirectoryContents GetDirectoryContents(string subpath) { + if (subpath == "") + { + subpath = "/"; + } + return _hybridFileProvider.GetDirectoryContents(subpath); } @@ -42,52 +46,12 @@ namespace Volo.Abp.VirtualFileSystem fileProviders.Add(dynamicFileProvider); - if (_options.FileSets.PhysicalPaths.Any()) + foreach (var fileSet in _options.FileSets.AsEnumerable().Reverse()) { - fileProviders.AddRange( - _options.FileSets.PhysicalPaths - .Select(rootPath => new PhysicalFileProvider(rootPath)) - .Reverse() - ); + fileProviders.Add(fileSet.FileProvider); } - fileProviders.Add(new InternalVirtualFileProvider(_options)); - return new CompositeFileProvider(fileProviders); } - - protected class InternalVirtualFileProvider : DictionaryBasedFileProvider - { - protected override IDictionary Files => _files.Value; - - private readonly AbpVirtualFileSystemOptions _options; - private readonly Lazy> _files; - - public InternalVirtualFileProvider(AbpVirtualFileSystemOptions options) - { - _options = options; - _files = new Lazy>( - CreateFiles, - true - ); - } - - private Dictionary CreateFiles() - { - var files = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var set in _options.FileSets) - { - set.AddFiles(files); - } - - return files; - } - - protected override string NormalizePath(string subpath) - { - return VirtualFilePathHelper.NormalizePath(subpath); - } - } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetInfo.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetInfo.cs new file mode 100644 index 0000000000..694729f02a --- /dev/null +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetInfo.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.FileProviders; + +namespace Volo.Abp.VirtualFileSystem +{ + public class VirtualFileSetInfo + { + public IFileProvider FileProvider { get; } + + public VirtualFileSetInfo([NotNull] IFileProvider fileProvider) + { + FileProvider = Check.NotNull(fileProvider, nameof(fileProvider)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetList.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetList.cs index 2ddd0ff3a4..371f8350d8 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetList.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetList.cs @@ -2,13 +2,8 @@ namespace Volo.Abp.VirtualFileSystem { - public class VirtualFileSetList : List + public class VirtualFileSetList : List { - public List PhysicalPaths { get; } - - public VirtualFileSetList() - { - PhysicalPaths = new List(); - } + } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetListExtensions.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetListExtensions.cs index 399f76f408..f1ca9749ea 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetListExtensions.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileSetListExtensions.cs @@ -1,44 +1,90 @@ using System; using System.IO; -using System.Linq; +using System.Reflection; using JetBrains.Annotations; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.FileProviders.Physical; using Volo.Abp.VirtualFileSystem.Embedded; +using Volo.Abp.VirtualFileSystem.Physical; namespace Volo.Abp.VirtualFileSystem { public static class VirtualFileSetListExtensions { - public static void AddEmbedded([NotNull] this VirtualFileSetList list, [CanBeNull] string baseNamespace = null, string baseFolderInProject = null) + public static void AddEmbedded( + [NotNull] this VirtualFileSetList list, + [CanBeNull] string baseNamespace = null, + [CanBeNull] string baseFolder = null) { Check.NotNull(list, nameof(list)); - list.Add( - new EmbeddedFileSet( - typeof(T).Assembly, - baseNamespace, - baseFolderInProject - ) + var assembly = typeof(T).Assembly; + var fileProvider = CreateFileProvider( + assembly, + baseNamespace, + baseFolder ); + + list.Add(new EmbeddedVirtualFileSetInfo(fileProvider, assembly, baseFolder)); } - public static void ReplaceEmbeddedByPhysical([NotNull] this VirtualFileSetList list, [NotNull] string pyhsicalPath) + public static void AddPhysical( + [NotNull] this VirtualFileSetList list, + [NotNull] string root, + ExclusionFilters exclusionFilters = ExclusionFilters.Sensitive) { Check.NotNull(list, nameof(list)); - Check.NotNull(pyhsicalPath, nameof(pyhsicalPath)); + Check.NotNullOrWhiteSpace(root, nameof(root)); - var assembly = typeof(T).Assembly; - var embeddedFileSets = list.OfType().Where(fs => fs.Assembly == assembly).ToList(); + var fileProvider = new PhysicalFileProvider(root, exclusionFilters); + list.Add(new PhysicalVirtualFileSetInfo(fileProvider, root)); + } + + private static IFileProvider CreateFileProvider( + [NotNull] Assembly assembly, + [CanBeNull] string baseNamespace = null, + [CanBeNull] string baseFolder = null) + { + Check.NotNull(assembly, nameof(assembly)); - foreach (var embeddedFileSet in embeddedFileSets) + var info = assembly.GetManifestResourceInfo("Microsoft.Extensions.FileProviders.Embedded.Manifest.xml"); + + if (info == null) { - list.Remove(embeddedFileSet); + return new AbpEmbeddedFileProvider(assembly, baseNamespace); + } + + if (baseFolder == null) + { + return new ManifestEmbeddedFileProvider(assembly); + } - if (!embeddedFileSet.BaseFolderInProject.IsNullOrEmpty()) + return new ManifestEmbeddedFileProvider(assembly, baseFolder); + } + + public static void ReplaceEmbeddedByPhysical( + [NotNull] this VirtualFileSetList fileSets, + [NotNull] string physicalPath) + { + Check.NotNull(fileSets, nameof(fileSets)); + Check.NotNullOrWhiteSpace(physicalPath, nameof(physicalPath)); + + var assembly = typeof(T).Assembly; + + for (var i = 0; i < fileSets.Count; i++) + { + if (fileSets[i] is EmbeddedVirtualFileSetInfo embeddedVirtualFileSet && + embeddedVirtualFileSet.Assembly == assembly) { - pyhsicalPath = Path.Combine(pyhsicalPath, embeddedFileSet.BaseFolderInProject); - } + var thisPath = physicalPath; - list.PhysicalPaths.Add(pyhsicalPath); + if (!embeddedVirtualFileSet.BaseFolder.IsNullOrEmpty()) + { + thisPath = Path.Combine(thisPath, embeddedVirtualFileSet.BaseFolder); + } + + fileSets[i] = new PhysicalVirtualFileSetInfo(new PhysicalFileProvider(thisPath), thisPath); + } } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs index 57cb40219b..6ce47c4679 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Shouldly; +using Volo.Abp.Validation; namespace Volo.Abp.AspNetCore.Mvc.Validation { @@ -31,6 +32,14 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation { return Content("ModelState.IsValid: " + ModelState.IsValid.ToString().ToLowerInvariant()); } + + [HttpGet] + [Route("object-result-action-dynamic-length")] + public Task ObjectResultActionDynamicLength(ValidationDynamicTestModel model) + { + ModelState.IsValid.ShouldBeTrue(); //AbpValidationFilter throws exception otherwise + return Task.FromResult(model.Value1); + } public class ValidationTest1Model { @@ -38,6 +47,28 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation [StringLength(5, MinimumLength = 2)] public string Value1 { get; set; } } + + public class ValidationDynamicTestModel + { + [DynamicStringLength(typeof(Consts), nameof(Consts.MaxValue1Length), nameof(Consts.MinValue1Length))] + public string Value1 { get; set; } + + [DynamicMaxLength(typeof(Consts), nameof(Consts.MaxValue2Length))] + public string Value2 { get; set; } + + [DynamicMaxLength(typeof(Consts), nameof(Consts.MaxValue3Length))] + public int[] Value3 { get; set; } + + public static class Consts + { + public static int MinValue1Length { get; set; } = 2; + public static int MaxValue1Length { get; set; } = 7; + + public static int MaxValue2Length { get; set; } = 4; + + public static int MaxValue3Length { get; set; } = 2; + } + } public class CustomValidateModel : IValidatableObject { @@ -51,6 +82,5 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation } } } - } -} +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs index 99e027f85a..9e22784ba7 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs @@ -58,5 +58,28 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation result.Error.ValidationErrors.ShouldContain(x => x.Message == "Value1 should be hello"); } + [Fact] + public async Task Should_Validate_Dynamic_Length_Object_Result_Success() + { + var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action-dynamic-length?value1=hello&value3[0]=53&value3[1]=54"); + result.ShouldBe("hello"); + + } + + [Fact] + public async Task Should_Validate_Dynamic_Length_Object_Result_Failing() + { + var result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=a", HttpStatusCode.BadRequest); //value1 has min string length of 2 chars. + result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); + + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=12345678", HttpStatusCode.BadRequest); //value1 has max string length of 7 chars. + result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); + + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=123458&value2=12345", HttpStatusCode.BadRequest); //value2 has max length of 5 chars. + result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); + + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value3[2]=55&value3[3]=56", HttpStatusCode.BadRequest); //value3 has max length of 2. + result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); + } } } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithAuditedAndHasCustomAuditingProperties.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithAuditedAndHasCustomAuditingProperties.cs new file mode 100644 index 0000000000..9ff57b286d --- /dev/null +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithAuditedAndHasCustomAuditingProperties.cs @@ -0,0 +1,26 @@ +using System; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Auditing.App.Entities +{ + [Audited] + public class AppEntityWithAuditedAndHasCustomAuditingProperties : AggregateRoot + { + protected AppEntityWithAuditedAndHasCustomAuditingProperties() + { + } + + public AppEntityWithAuditedAndHasCustomAuditingProperties(Guid id) + : base(id) + { + } + + public DateTime? CreationTime { get; set; } + public Guid? CreatorId { get; set; } + public DateTime? LastModificationTime { get; set; } + public Guid? LastModifierId { get; set; } + public bool IsDeleted { get; set; } + public DateTime? DeletionTime { get; set; } + public Guid? DeleterId { get; set; } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppFullAuditedEntityWithAudited.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppFullAuditedEntityWithAudited.cs new file mode 100644 index 0000000000..def7e0d44c --- /dev/null +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppFullAuditedEntityWithAudited.cs @@ -0,0 +1,21 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Volo.Abp.Auditing.App.Entities +{ + [Audited] + public class AppFullAuditedEntityWithAudited : FullAuditedAggregateRoot + { + protected AppFullAuditedEntityWithAudited() + { + } + + public AppFullAuditedEntityWithAudited(Guid id, string name) + : base(id) + { + Name = name; + } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs index 0968bf2cc2..d6b2d2b167 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs @@ -17,6 +17,10 @@ namespace Volo.Abp.Auditing.App.EntityFrameworkCore public DbSet AppEntityWithPropertyHasAudited { get; set; } public DbSet AppEntityWithSelector { get; set; } + + public DbSet AppFullAuditedEntityWithAudited { get; set; } + + public DbSet AppEntityWithAuditedAndHasCustomAuditingProperties { get; set; } public AbpAuditingTestDbContext(DbContextOptions options) : base(options) diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index 2887eb8510..8c647c92c4 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -215,5 +216,63 @@ namespace Volo.Abp.Auditing #pragma warning restore 4014 } + private static List GetBaseAuditPropertyNames() + { + return new List + { + nameof(IHasCreationTime.CreationTime), + nameof(IMustHaveCreator.CreatorId), + nameof(IHasModificationTime.LastModificationTime), + nameof(IModificationAuditedObject.LastModifierId), + nameof(ISoftDelete.IsDeleted), + nameof(IHasDeletionTime.DeletionTime), + nameof(IDeletionAuditedObject.DeleterId) + }; + } + + [Fact] + public virtual async Task Should_Write_AuditLog_Ignoring_Base_Auditing_Properties_For_Entity_That_Has_Audited_Attribute() + { + using (var scope = _auditingManager.BeginScope()) + { + var repository = ServiceProvider.GetRequiredService>(); + await repository.InsertAsync(new AppFullAuditedEntityWithAudited(Guid.NewGuid(), "test name")); + await scope.SaveAsync(); + } + +#pragma warning disable 4014 + _auditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 + && x.EntityChanges[0].PropertyChanges.Any(y => + !GetBaseAuditPropertyNames().Contains(y.PropertyName)))); +#pragma warning restore 4014 + } + + [Fact] + public virtual async Task Should_Write_AuditLog_Including_Custom_Base_Auditing_Properties_For_Entity_That_Has_Audited_Attribute() + { + using (var scope = _auditingManager.BeginScope()) + { + var repository = ServiceProvider.GetRequiredService>(); + await repository.InsertAsync(new AppEntityWithAuditedAndHasCustomAuditingProperties(Guid.NewGuid()) + { + CreationTime = DateTime.Now, + CreatorId = Guid.NewGuid(), + LastModificationTime = DateTime.Now, + LastModifierId = Guid.NewGuid(), + IsDeleted = true, + DeletionTime = DateTime.Now, + DeleterId = Guid.NewGuid() + }); + await scope.SaveAsync(); + } + +#pragma warning disable 4014 + _auditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 + && x.EntityChanges[0].PropertyChanges + .Where(y => y.PropertyName != nameof(AppEntityWithAuditedAndHasCustomAuditingProperties + .ExtraProperties)) + .All(y => GetBaseAuditPropertyNames().Contains(y.PropertyName)))); +#pragma warning restore 4014 + } } } diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNamingNormalizerProvider_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNamingNormalizerProvider_Tests.cs new file mode 100644 index 0000000000..23df740630 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNamingNormalizerProvider_Tests.cs @@ -0,0 +1,57 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.BlobStoring.Azure +{ + public class DefaultAzureBlobNamingNormalizerProvider_Tests : AbpBlobStoringAzureTestCommonBase + { + private readonly IBlobNamingNormalizer _blobNamingNormalizer; + + public DefaultAzureBlobNamingNormalizerProvider_Tests() + { + _blobNamingNormalizer = GetRequiredService(); + } + + [Fact] + public void NormalizeContainerName_Lowercase() + { + var filename = "ThisIsMyContainerName"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismycontainername"); + } + + [Fact] + public void NormalizeContainerName_Only_Letters_Numbers_Dash() + { + var filename = ",./this-i,./s-my-c,./ont,./ai+*/.=!@#$n^&*er-name.+/"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + [Fact] + public void NormalizeContainerName_Dash() + { + var filename = "-this--is----my-container----name-"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + + [Fact] + public void NormalizeContainerName_Min_Length() + { + var filename = "a"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeGreaterThanOrEqualTo(3); + } + + + [Fact] + public void NormalizeContainerName_Max_Length() + { + var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeLessThanOrEqualTo(63); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/DefaultFileSystemBlobNamingNormalizerProvider_Tests.cs b/framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/DefaultFileSystemBlobNamingNormalizerProvider_Tests.cs new file mode 100644 index 0000000000..b3f9fcf5d1 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/DefaultFileSystemBlobNamingNormalizerProvider_Tests.cs @@ -0,0 +1,62 @@ +using System.Runtime; +using System.Runtime.InteropServices; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Shouldly; +using Xunit; + +namespace Volo.Abp.BlobStoring.FileSystem +{ + public class DefaultFileSystemBlobNamingNormalizerProvider_Tests : AbpBlobStoringFileSystemTestBase + { + private readonly IBlobNamingNormalizer _blobNamingNormalizer; + + public DefaultFileSystemBlobNamingNormalizerProvider_Tests() + { + _blobNamingNormalizer = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + var _iosPlatformProvider = Substitute.For(); + _iosPlatformProvider.GetCurrentOSPlatform().Returns(OSPlatform.Windows); + services.AddSingleton(_iosPlatformProvider); + } + + [Fact] + public void NormalizeContainerName() + { + var filename = "thisismy:*?\"<>|foldername"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismyfoldername"); + } + + [Fact] + public void NormalizeBlobName() + { + var filename = "thisismy:*?\"<>|filename"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismyfilename"); + } + + [Theory] + [InlineData("/")] + [InlineData("\\")] + public void NormalizeContainerName_With_Directory(string delimiter) + { + var filename = $"thisis{delimiter}my:*?\"<>|{delimiter}foldername"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe($"thisis{delimiter}my{delimiter}foldername"); + } + + [Theory] + [InlineData("/")] + [InlineData("\\")] + public void NormalizeBlobName_With_Directory(string delimiter) + { + var filename = $"thisis{delimiter}my:*?\"<>|{delimiter}filename"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe($"thisis{delimiter}my{delimiter}filename"); + } + } +} diff --git a/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo.Abp.Caching.StackExchangeRedis.Tests.csproj b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo.Abp.Caching.StackExchangeRedis.Tests.csproj new file mode 100644 index 0000000000..15f4bc8b9d --- /dev/null +++ b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo.Abp.Caching.StackExchangeRedis.Tests.csproj @@ -0,0 +1,17 @@ + + + + + + netcoreapp3.1 + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisTestBase.cs b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisTestBase.cs new file mode 100644 index 0000000000..354ce3cc2b --- /dev/null +++ b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisTestBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.Caching.StackExchangeRedis +{ + public abstract class AbpCachingStackExchangeRedisTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisTestModule.cs b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisTestModule.cs new file mode 100644 index 0000000000..5d67ae743a --- /dev/null +++ b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisTestModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Caching.StackExchangeRedis +{ + [DependsOn( + typeof(AbpCachingStackExchangeRedisModule), + typeof(AbpTestBaseModule), + typeof(AbpAutofacModule) + )] + public class AbpCachingStackExchangeRedisTestModule : AbpModule + { + + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs new file mode 100644 index 0000000000..660c8dbdb7 --- /dev/null +++ b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Caching.Distributed; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Caching.StackExchangeRedis +{ + public class AbpRedisCache_Tests : AbpCachingStackExchangeRedisTestBase + { + private readonly IDistributedCache _distributedCache; + + public AbpRedisCache_Tests() + { + _distributedCache = GetRequiredService(); + } + + [Fact] + public void Should_Replace_RedisCache() + { + (_distributedCache is AbpRedisCache).ShouldBeTrue(); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index 14677a73bd..533362c851 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; @@ -80,7 +81,6 @@ namespace Volo.Abp.Caching var personCache = GetRequiredService>(); var otherPersonCache = GetRequiredService>(); - var cacheKey = Guid.NewGuid().ToString(); const string personName = "john nash"; @@ -310,5 +310,34 @@ namespace Volo.Abp.Caching cacheItem2.ShouldBeNull(); } + [Fact] + public async Task Should_Set_And_Get_Multiple_Items_Async() + { + var personCache = GetRequiredService>(); + + await personCache.SetManyAsync(new[] + { + new KeyValuePair("john", new PersonCacheItem("John Nash")), + new KeyValuePair("thomas", new PersonCacheItem("Thomas Moore")) + }); + + var cacheItems = await personCache.GetManyAsync(new[] + { + "john", + "thomas", + "baris" //doesn't exist + }); + + cacheItems.Length.ShouldBe(3); + cacheItems[0].Key.ShouldBe("john"); + cacheItems[0].Value.Name.ShouldBe("John Nash"); + cacheItems[1].Key.ShouldBe("thomas"); + cacheItems[1].Value.Name.ShouldBe("Thomas Moore"); + cacheItems[2].Key.ShouldBe("baris"); + cacheItems[2].Value.ShouldBeNull(); + + (await personCache.GetAsync("john")).Name.ShouldBe("John Nash"); + (await personCache.GetAsync("baris")).ShouldBeNull(); + } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj index 9f8f904aa4..bcb9dcbf74 100644 --- a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj +++ b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj @@ -4,6 +4,7 @@ netcoreapp3.1 + true @@ -15,6 +16,7 @@ + diff --git a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/MyResources/js/my{test}.2.9.min.js b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/MyResources/js/my{test}.2.9.min.js new file mode 100644 index 0000000000..1c74f24545 --- /dev/null +++ b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/MyResources/js/my{test}.2.9.min.js @@ -0,0 +1 @@ +//my{test}.2.9.min.js-content \ No newline at end of file diff --git a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/VirtualFileProvider_Tests.cs b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/VirtualFileProvider_Tests.cs index 2243d9a11a..aae01ff912 100644 --- a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/VirtualFileProvider_Tests.cs +++ b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/VirtualFileProvider_Tests.cs @@ -1,5 +1,4 @@ -using System; -using System.IO; +using System.IO; using System.Linq; using System.Text; using Microsoft.Extensions.DependencyInjection; @@ -34,6 +33,22 @@ namespace Volo.Abp.VirtualFileSystem Encoding.UTF8.GetString(stream.GetAllBytes()).ShouldBe("//jquery-3-1-1-min.js-contents"); } } + + [Fact] + public void Should_Define_And_Get_Embedded_Resources_With_Special_Chars() + { + //Act + var resource = _virtualFileProvider.GetFileInfo("/js/my{test}.2.9.min.js"); + + //Assert + resource.ShouldNotBeNull(); + resource.Exists.ShouldBeTrue(); + + using (var stream = resource.CreateReadStream()) + { + Encoding.UTF8.GetString(stream.GetAllBytes()).ShouldBe("//my{test}.2.9.min.js-content"); + } + } [Fact] public void Should_Define_And_Get_Embedded_Directory_Contents() @@ -45,7 +60,9 @@ namespace Volo.Abp.VirtualFileSystem contents.Exists.ShouldNotBeNull(); var contentList = contents.ToList(); + contentList.ShouldContain(x => x.Name == "jquery-3-1-1-min.js"); + contentList.ShouldContain(x => x.Name == "my{test}.2.9.min.js"); } [Theory] @@ -70,7 +87,9 @@ namespace Volo.Abp.VirtualFileSystem { Configure(options => { - options.FileSets.AddEmbedded("Volo.Abp.VirtualFileSystem.MyResources"); + options.FileSets.AddEmbedded( + baseFolder: "/Volo/Abp/VirtualFileSystem/MyResources" + ); }); } } diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json index 7cf2cf7be6..f0dbdaadb6 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json @@ -40,6 +40,9 @@ "DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Is self-registration enabled", "Description:Abp.Account.IsSelfRegistrationEnabled": "Whether a user can register the account by him or herself.", "DisplayName:Abp.Account.EnableLocalLogin": "Authenticate with a local account", - "Description:Abp.Account.EnableLocalLogin": "Indicates if the server will allow users to authenticate with a local account." + "Description:Abp.Account.EnableLocalLogin": "Indicates if the server will allow users to authenticate with a local account.", + "LoggedOutTitle": "Signed Out", + "LoggedOutText": "You have been signed out and you will be redirected soon.", + "ReturnToText": "Click here to redirect to {0}" } } diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json index 60d5f40634..3938a34984 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json @@ -40,6 +40,9 @@ "DisplayName:Abp.Account.IsSelfRegistrationEnabled": "self-registration etkin mi ?", "Description:Abp.Account.IsSelfRegistrationEnabled": "Bir kullanıcının hesabı kendisi tarafından kaydedip kaydedememesidir.", "DisplayName:Abp.Account.EnableLocalLogin": "Yerel bir hesapla kimlik doğrulaması", - "Description:Abp.Account.EnableLocalLogin": "Sunucunun, kullanıcıların yerel bir hesapla kimlik doğrulamasına izin verip vermeyeceğini belirtir." + "Description:Abp.Account.EnableLocalLogin": "Sunucunun, kullanıcıların yerel bir hesapla kimlik doğrulamasına izin verip vermeyeceğini belirtir.", + "LoggedOutTitle": "Çıkış Yaptınız", + "LoggedOutText": "Çıkış yaptınız ve birazdan yönlendirileceksiniz.", + "ReturnToText": "{0} uygulamasına dönmek için tıklayın." } -} \ No newline at end of file +} diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/RegisterDto.cs b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/RegisterDto.cs index 9a9f3caa4e..b69383d26f 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/RegisterDto.cs +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/RegisterDto.cs @@ -1,22 +1,23 @@ using System.ComponentModel.DataAnnotations; using Volo.Abp.Auditing; using Volo.Abp.Identity; +using Volo.Abp.Validation; namespace Volo.Abp.Account { public class RegisterDto { [Required] - [StringLength(IdentityUserConsts.MaxUserNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] public string UserName { get; set; } [Required] [EmailAddress] - [StringLength(IdentityUserConsts.MaxEmailLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] public string EmailAddress { get; set; } [Required] - [StringLength(IdentityUserConsts.MaxPasswordLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] [DataType(DataType.Password)] [DisableAuditing] public string Password { get; set; } diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs index ab403ea5a1..43857cc111 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs @@ -1,6 +1,8 @@ -using IdentityServer4.Services; +using System.Security.Claims; +using IdentityServer4.Services; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; namespace Volo.Abp.Account.Web.Pages.Account @@ -24,13 +26,20 @@ namespace Volo.Abp.Account.Web.Pages.Account if (!string.IsNullOrEmpty(logoutId)) { var logoutContext = await Interaction.GetLogoutContextAsync(logoutId); + await SignInManager.SignOutAsync(); - var postLogoutUri = logoutContext.PostLogoutRedirectUri; + HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - if (!string.IsNullOrEmpty(postLogoutUri)) + LoggedOutModel vm = new LoggedOutModel() { - return Redirect(postLogoutUri); - } + PostLogoutRedirectUri = logoutContext?.PostLogoutRedirectUri, + ClientName = logoutContext?.ClientName, + SignOutIframeUrl = logoutContext?.SignOutIFrameUrl + }; + + Logger.LogInformation($"Redirecting to LoggedOut Page..."); + + return RedirectToPage("./LoggedOut", vm); } if (ReturnUrl != null) @@ -38,6 +47,8 @@ namespace Volo.Abp.Account.Web.Pages.Account return LocalRedirect(ReturnUrl); } + Logger.LogInformation( + $"IdentityServerSupportedLogoutModel couldn't find postLogoutUri... Redirecting to:/Account/Login.."); return RedirectToPage("/Account/Login"); } } diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Properties/launchSettings.json b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Properties/launchSettings.json deleted file mode 100644 index aca3a5b165..0000000000 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:49583/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Account.Web.IdentityServer": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:49584/" - } - } -} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 17a164e612..ca27f60695 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -21,8 +21,6 @@ - - diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml new file mode 100644 index 0000000000..0e7b14236a --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml @@ -0,0 +1,31 @@ +@page "/Account/LoggedOut" +@model Volo.Abp.Account.Web.Pages.Account.LoggedOutModel +@using Volo.Abp.Account.Localization +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Web.Pages.Account +@inject IHtmlLocalizer L + +@section scripts { + + + +} + +@section styles { + +} + + + + @L["LoggedOutTitle"] + @L["LoggedOutText"] + @if (Model.PostLogoutRedirectUri != null) + { + @L["ReturnToText", Model.ClientName] + } + @if (Model.SignOutIframeUrl != null) + { + + } + + diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml.cs new file mode 100644 index 0000000000..6023732975 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public class LoggedOutModel : AccountPageModel + { + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ClientName { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string SignOutIframeUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string PostLogoutRedirectUri { get; set; } + + public virtual Task OnGetAsync() + { + return Task.FromResult(Page()); + } + + public virtual Task OnPostAsync() + { + return Task.FromResult(Page()); + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.css b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.css new file mode 100644 index 0000000000..75dcda385e --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.css @@ -0,0 +1,5 @@ +.logoutiframe { + display: none; + width:0; + height: 0; +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js new file mode 100644 index 0000000000..950bf65719 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js @@ -0,0 +1,5 @@ +document.addEventListener("DOMContentLoaded", function (event) { + setTimeout(function () { + window.location = document.getElementById("redirectButton").getAttribute("href"); + }, 3000) +}); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 34a084bd3d..5251cb12f1 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -254,11 +254,11 @@ namespace Volo.Abp.Account.Web.Pages.Account public class LoginInputModel { [Required] - [StringLength(IdentityUserConsts.MaxEmailLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] public string UserNameOrEmailAddress { get; set; } [Required] - [StringLength(IdentityUserConsts.MaxPasswordLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] [DataType(DataType.Password)] [DisableAuditing] public string Password { get; set; } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs index deed56b736..3c779dc870 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Volo.Abp.Identity; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Validation; namespace Volo.Abp.Account.Web.Pages.Account { @@ -36,44 +37,45 @@ namespace Volo.Abp.Account.Web.Pages.Account public class ChangePasswordInfoModel { [Required] - [StringLength(IdentityUserConsts.MaxPasswordLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] [Display(Name = "DisplayName:CurrentPassword")] [DataType(DataType.Password)] public string CurrentPassword { get; set; } [Required] - [StringLength(IdentityUserConsts.MaxPasswordLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] [Display(Name = "DisplayName:NewPassword")] [DataType(DataType.Password)] public string NewPassword { get; set; } [Required] - [StringLength(IdentityUserConsts.MaxPasswordLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] [Display(Name = "DisplayName:NewPasswordConfirm")] [DataType(DataType.Password)] public string NewPasswordConfirm { get; set; } } + public class PersonalSettingsInfoModel { [Required] - [StringLength(IdentityUserConsts.MaxUserNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] [Display(Name = "DisplayName:UserName")] public string UserName { get; set; } [Required] - [StringLength(IdentityUserConsts.MaxEmailLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] [Display(Name = "DisplayName:Email")] public string Email { get; set; } - [StringLength(IdentityUserConsts.MaxNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))] [Display(Name = "DisplayName:Name")] public string Name { get; set; } - [StringLength(IdentityUserConsts.MaxSurnameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))] [Display(Name = "DisplayName:Surname")] public string Surname { get; set; } - [StringLength(IdentityUserConsts.MaxPhoneNumberLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))] [Display(Name = "DisplayName:PhoneNumber")] public string PhoneNumber { get; set; } } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index 87c7131305..f6d626dd63 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -9,6 +9,7 @@ using Volo.Abp.Application.Dtos; using Volo.Abp.Identity; using Volo.Abp.Settings; using Volo.Abp.Uow; +using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace Volo.Abp.Account.Web.Pages.Account @@ -74,16 +75,16 @@ namespace Volo.Abp.Account.Web.Pages.Account public class PostInput { [Required] - [StringLength(IdentityUserConsts.MaxUserNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] public string UserName { get; set; } [Required] [EmailAddress] - [StringLength(IdentityUserConsts.MaxEmailLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] public string EmailAddress { get; set; } [Required] - [StringLength(IdentityUserConsts.MaxPasswordLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] [DataType(DataType.Password)] [DisableAuditing] public string Password { get; set; } diff --git a/modules/account/src/Volo.Abp.Account.Web/Properties/launchSettings.json b/modules/account/src/Volo.Abp.Account.Web/Properties/launchSettings.json deleted file mode 100644 index 8dab775bff..0000000000 --- a/modules/account/src/Volo.Abp.Account.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:56009/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Account.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:56013/" - } - } -} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index a77cbad333..42918e34d4 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -25,8 +25,6 @@ - - diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogActionConsts.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogActionConsts.cs index 86652aacec..5f1967804e 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogActionConsts.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogActionConsts.cs @@ -2,10 +2,19 @@ { public class AuditLogActionConsts { - public const int MaxServiceNameLength = 256; + /// + /// Default value: 256 + /// + public static int MaxServiceNameLength { get; set; } = 256; - public const int MaxMethodNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxMethodNameLength { get; set; } = 128; - public const int MaxParametersLength = 2000; + /// + /// Default value: 2000 + /// + public static int MaxParametersLength { get; set; } = 2000; } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs index 504e7451e0..4a8c723b76 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs @@ -2,31 +2,67 @@ { public static class AuditLogConsts { - public const int MaxApplicationNameLength = 96; + /// + /// Default value: 96 + /// + public static int MaxApplicationNameLength { get; set; } = 96; - public const int MaxClientIpAddressLength = 64; + /// + /// Default value: 64 + /// + public static int MaxClientIpAddressLength { get; set; } = 64; - public const int MaxClientNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxClientNameLength { get; set; } = 128; - public const int MaxClientIdLength = 64; + /// + /// Default value: 64 + /// + public static int MaxClientIdLength { get; set; } = 64; - public const int MaxCorrelationIdLength = 64; + /// + /// Default value: 64 + /// + public static int MaxCorrelationIdLength { get; set; } = 64; - public const int MaxBrowserInfoLength = 512; + /// + /// Default value: 512 + /// + public static int MaxBrowserInfoLength { get; set; } = 512; - public const int MaxExceptionsLength = 4000; + /// + /// Default value: 4000 + /// + public static int MaxExceptionsLength { get; set; } = 4000; //TODO: Replace with MaxExceptionsLength in v3.0 public static int MaxExceptionsLengthValue { get; set; } = MaxExceptionsLength; - public const int MaxCommentsLength = 256; + /// + /// Default value: 256 + /// + public static int MaxCommentsLength { get; set; } = 256; - public const int MaxUrlLength = 256; + /// + /// Default value: 256 + /// + public static int MaxUrlLength { get; set; } = 256; - public const int MaxHttpMethodLength = 16; + /// + /// Default value: 16 + /// + public static int MaxHttpMethodLength { get; set; } = 16; - public const int MaxUserNameLength = 256; + /// + /// Default value: 256 + /// + public static int MaxUserNameLength { get; set; } = 256; - public const int MaxTenantNameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxTenantNameLength { get; set; } = 64; } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/EntityChangeConsts.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/EntityChangeConsts.cs index d19407cfa4..b7f88db3e2 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/EntityChangeConsts.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/EntityChangeConsts.cs @@ -2,8 +2,14 @@ { public class EntityChangeConsts { - public const int MaxEntityTypeFullNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxEntityTypeFullNameLength { get; set; } = 128; - public const int MaxEntityIdLength = 128; + /// + /// Default value: 128 + /// + public static int MaxEntityIdLength { get; set; } = 128; } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/EntityPropertyChangeConsts.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/EntityPropertyChangeConsts.cs index 2ac9204395..8d592e06aa 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/EntityPropertyChangeConsts.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/EntityPropertyChangeConsts.cs @@ -2,12 +2,24 @@ { public class EntityPropertyChangeConsts { - public const int MaxNewValueLength = 512; + /// + /// Default value: 512 + /// + public static int MaxNewValueLength { get; set; } = 512; - public const int MaxOriginalValueLength = 512; + /// + /// Default value: 512 + /// + public static int MaxOriginalValueLength { get; set; } = 512; - public const int MaxPropertyNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxPropertyNameLength { get; set; } = 128; - public const int MaxPropertyTypeFullNameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxPropertyTypeFullNameLength { get; set; } = 64; } } diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20180724105416_BackgroundJobs_Initial.Designer.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20180724105416_BackgroundJobs_Initial.Designer.cs deleted file mode 100644 index acab1e91e7..0000000000 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20180724105416_BackgroundJobs_Initial.Designer.cs +++ /dev/null @@ -1,66 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.Abp.BackgroundJobs; -using Volo.Abp.BackgroundJobs.DemoApp.Db; - -namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations -{ - [DbContext(typeof(DemoAppDbContext))] - [Migration("20180724105416_BackgroundJobs_Initial")] - partial class BackgroundJobs_Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-rtm-30799") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("CreationTime") - .HasColumnName("CreationTime"); - - b.Property("IsAbandoned") - .ValueGeneratedOnAdd() - .HasDefaultValue(false); - - b.Property("JobArgs") - .IsRequired() - .HasMaxLength(1048576); - - b.Property("JobName") - .IsRequired() - .HasMaxLength(128); - - b.Property("LastTryTime"); - - b.Property("NextTryTime"); - - b.Property("Priority") - .ValueGeneratedOnAdd() - .HasDefaultValue((byte)15); - - b.Property("TryCount") - .ValueGeneratedOnAdd() - .HasDefaultValue((short)0); - - b.HasKey("Id"); - - b.HasIndex("IsAbandoned", "NextTryTime"); - - b.ToTable("AbpBackgroundJobs"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20181211140407_AggragateRoot_Changes.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20181211140407_AggragateRoot_Changes.cs deleted file mode 100644 index 25aa036ed3..0000000000 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20181211140407_AggragateRoot_Changes.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations -{ - public partial class AggragateRoot_Changes : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ConcurrencyStamp", - table: "AbpBackgroundJobs", - nullable: true); - - migrationBuilder.AddColumn( - name: "ExtraProperties", - table: "AbpBackgroundJobs", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ConcurrencyStamp", - table: "AbpBackgroundJobs"); - - migrationBuilder.DropColumn( - name: "ExtraProperties", - table: "AbpBackgroundJobs"); - } - } -} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20181211140407_AggragateRoot_Changes.Designer.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs similarity index 59% rename from modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20181211140407_AggragateRoot_Changes.Designer.cs rename to modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs index d4ed20351c..40c0adcee7 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20181211140407_AggragateRoot_Changes.Designer.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs @@ -5,58 +5,73 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.Abp.BackgroundJobs; using Volo.Abp.BackgroundJobs.DemoApp.Db; +using Volo.Abp.EntityFrameworkCore; namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations { [DbContext(typeof(DemoAppDbContext))] - [Migration("20181211140407_AggragateRoot_Changes")] - partial class AggragateRoot_Changes + [Migration("20200624023633_BackgroundJobs_Initial")] + partial class BackgroundJobs_Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-rtm-30799") + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.5") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") - .HasColumnName("CreationTime"); + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); b.Property("IsAbandoned") .ValueGeneratedOnAdd() + .HasColumnType("bit") .HasDefaultValue(false); b.Property("JobArgs") .IsRequired() + .HasColumnType("nvarchar(max)") .HasMaxLength(1048576); b.Property("JobName") .IsRequired() + .HasColumnType("nvarchar(128)") .HasMaxLength(128); - b.Property("LastTryTime"); + b.Property("LastTryTime") + .HasColumnType("datetime2"); - b.Property("NextTryTime"); + b.Property("NextTryTime") + .HasColumnType("datetime2"); b.Property("Priority") .ValueGeneratedOnAdd() + .HasColumnType("tinyint") .HasDefaultValue((byte)15); b.Property("TryCount") .ValueGeneratedOnAdd() + .HasColumnType("smallint") .HasDefaultValue((short)0); b.HasKey("Id"); diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20180724105416_BackgroundJobs_Initial.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs similarity index 91% rename from modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20180724105416_BackgroundJobs_Initial.cs rename to modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs index 76202a0748..eebffa859a 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20180724105416_BackgroundJobs_Initial.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs @@ -12,6 +12,8 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations columns: table => new { Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), JobName = table.Column(maxLength: 128, nullable: false), JobArgs = table.Column(maxLength: 1048576, nullable: false), TryCount = table.Column(nullable: false, defaultValue: (short)0), diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs index d58d7a4966..fa77fd75ca 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs @@ -4,8 +4,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.Abp.BackgroundJobs; using Volo.Abp.BackgroundJobs.DemoApp.Db; +using Volo.Abp.EntityFrameworkCore; namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations { @@ -16,45 +16,60 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-rtm-30799") + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.5") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") - .HasColumnName("CreationTime"); + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); b.Property("IsAbandoned") .ValueGeneratedOnAdd() + .HasColumnType("bit") .HasDefaultValue(false); b.Property("JobArgs") .IsRequired() + .HasColumnType("nvarchar(max)") .HasMaxLength(1048576); b.Property("JobName") .IsRequired() + .HasColumnType("nvarchar(128)") .HasMaxLength(128); - b.Property("LastTryTime"); + b.Property("LastTryTime") + .HasColumnType("datetime2"); - b.Property("NextTryTime"); + b.Property("NextTryTime") + .HasColumnType("datetime2"); b.Property("Priority") .ValueGeneratedOnAdd() + .HasColumnType("tinyint") .HasDefaultValue((byte)15); b.Property("TryCount") .ValueGeneratedOnAdd() + .HasColumnType("smallint") .HasDefaultValue((short)0); b.HasKey("Id"); diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain.Shared/Volo/Abp/BackgroundJobs/BackgroundJobRecordConsts.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain.Shared/Volo/Abp/BackgroundJobs/BackgroundJobRecordConsts.cs index f4418bc423..01f935b30e 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain.Shared/Volo/Abp/BackgroundJobs/BackgroundJobRecordConsts.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain.Shared/Volo/Abp/BackgroundJobs/BackgroundJobRecordConsts.cs @@ -2,8 +2,14 @@ { public static class BackgroundJobRecordConsts { - public const int MaxJobNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxJobNameLength { get; set; } = 128; - public const int MaxJobArgsLength = 1024 * 1024; + /// + /// Default value: 1024 * 1024 + /// + public static int MaxJobArgsLength { get; set; } = 1024 * 1024; } } diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531134550_Initial.Designer.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531134550_Initial.Designer.cs deleted file mode 100644 index 23fe326a47..0000000000 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531134550_Initial.Designer.cs +++ /dev/null @@ -1,97 +0,0 @@ -// -using System; -using BlobStoring.Database.Host.ConsoleApp.ConsoleApp.EfCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.Abp.EntityFrameworkCore; - -namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations -{ - [DbContext(typeof(BlobStoringHostDbContext))] - [Migration("20200531134550_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.4") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Volo.Abp.BlobStoring.Database.DatabaseBlob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("ContainerId") - .HasColumnType("uniqueidentifier"); - - b.Property("Content") - .HasColumnType("varbinary(max)") - .HasMaxLength(2147483647); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("TenantId", "ContainerId", "Name"); - - b.ToTable("AbpBlobs"); - }); - - modelBuilder.Entity("Volo.Abp.BlobStoring.Database.DatabaseBlobContainer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("TenantId", "Name"); - - b.ToTable("AbpBlobContainers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531143839_FK_Added.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531143839_FK_Added.cs deleted file mode 100644 index 4411063f94..0000000000 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531143839_FK_Added.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations -{ - public partial class FK_Added : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_AbpBlobs_ContainerId", - table: "AbpBlobs", - column: "ContainerId"); - - migrationBuilder.AddForeignKey( - name: "FK_AbpBlobs_AbpBlobContainers_ContainerId", - table: "AbpBlobs", - column: "ContainerId", - principalTable: "AbpBlobContainers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_AbpBlobs_AbpBlobContainers_ContainerId", - table: "AbpBlobs"); - - migrationBuilder.DropIndex( - name: "IX_AbpBlobs_ContainerId", - table: "AbpBlobs"); - } - } -} diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531143839_FK_Added.Designer.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs similarity index 92% rename from modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531143839_FK_Added.Designer.cs rename to modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs index 1214d7f500..5aa34b1eda 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531143839_FK_Added.Designer.cs +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs @@ -11,15 +11,15 @@ using Volo.Abp.EntityFrameworkCore; namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations { [DbContext(typeof(BlobStoringHostDbContext))] - [Migration("20200531143839_FK_Added")] - partial class FK_Added + [Migration("20200624023744_Initial")] + partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("ProductVersion", "3.1.5") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -32,7 +32,8 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("ContainerId") .HasColumnType("uniqueidentifier"); @@ -72,7 +73,8 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("ExtraProperties") .HasColumnName("ExtraProperties") diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531134550_Initial.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs similarity index 76% rename from modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531134550_Initial.cs rename to modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs index d1ee75ac7f..bc8d412cb5 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200531134550_Initial.cs +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs @@ -13,7 +13,7 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations { Id = table.Column(nullable: false), ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), TenantId = table.Column(nullable: true), Name = table.Column(maxLength: 128, nullable: false) }, @@ -28,7 +28,7 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations { Id = table.Column(nullable: false), ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), ContainerId = table.Column(nullable: false), TenantId = table.Column(nullable: true), Name = table.Column(maxLength: 256, nullable: false), @@ -37,6 +37,12 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations constraints: table => { table.PrimaryKey("PK_AbpBlobs", x => x.Id); + table.ForeignKey( + name: "FK_AbpBlobs_AbpBlobContainers_ContainerId", + column: x => x.ContainerId, + principalTable: "AbpBlobContainers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( @@ -44,6 +50,11 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations table: "AbpBlobContainers", columns: new[] { "TenantId", "Name" }); + migrationBuilder.CreateIndex( + name: "IX_AbpBlobs_ContainerId", + table: "AbpBlobs", + column: "ContainerId"); + migrationBuilder.CreateIndex( name: "IX_AbpBlobs_TenantId_ContainerId_Name", table: "AbpBlobs", @@ -53,10 +64,10 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "AbpBlobContainers"); + name: "AbpBlobs"); migrationBuilder.DropTable( - name: "AbpBlobs"); + name: "AbpBlobContainers"); } } } diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs index 278eb1f72c..796c68b0ad 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("ProductVersion", "3.1.5") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -30,7 +30,8 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("ContainerId") .HasColumnType("uniqueidentifier"); @@ -70,7 +71,8 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("ExtraProperties") .HasColumnName("ExtraProperties") diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/DatabaseContainerConsts.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/DatabaseContainerConsts.cs index 0a71451f9f..22d127a1f1 100644 --- a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/DatabaseContainerConsts.cs +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/DatabaseContainerConsts.cs @@ -2,6 +2,9 @@ { public static class DatabaseContainerConsts { - public const int MaxNameLength = 128; + /// + /// Default value: 128. + /// + public static int MaxNameLength { get; set; } = 128; } } \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs index b2d89fbd55..6f4bae5e63 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Validation; using Volo.Blogging.Admin.Blogs; using Volo.Blogging.Blogs; @@ -44,14 +45,14 @@ namespace Volo.Blogging.Admin.Pages.Blogging.Admin.Blogs public class BlogCreateModalView { [Required] - [StringLength(BlogConsts.MaxNameLength)] + [DynamicStringLength(typeof(BlogConsts), nameof(BlogConsts.MaxNameLength))] public string Name { get; set; } [Required] - [StringLength(BlogConsts.MaxShortNameLength)] + [DynamicStringLength(typeof(BlogConsts), nameof(BlogConsts.MaxShortNameLength))] public string ShortName { get; set; } - [StringLength(BlogConsts.MaxDescriptionLength)] + [DynamicStringLength(typeof(BlogConsts), nameof(BlogConsts.MaxDescriptionLength))] public string Description { get; set; } } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs index 536810e015..8d0b2ba978 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Validation; using Volo.Blogging.Admin.Blogs; using Volo.Blogging.Blogs; @@ -58,14 +59,14 @@ namespace Volo.Blogging.Admin.Pages.Blogging.Admin.Blogs public Guid Id { get; set; } [Required] - [StringLength(BlogConsts.MaxNameLength)] + [DynamicStringLength(typeof(BlogConsts), nameof(BlogConsts.MaxNameLength))] public string Name { get; set; } [Required] - [StringLength(BlogConsts.MaxShortNameLength)] + [DynamicStringLength(typeof(BlogConsts), nameof(BlogConsts.MaxShortNameLength))] public string ShortName { get; set; } - [StringLength(BlogConsts.MaxDescriptionLength)] + [DynamicStringLength(typeof(BlogConsts), nameof(BlogConsts.MaxDescriptionLength))] public string Description { get; set; } } } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Properties/launchSettings.json b/modules/blogging/src/Volo.Blogging.Admin.Web/Properties/launchSettings.json deleted file mode 100644 index 64a5fd912c..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:50000/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Blogging.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:50014/" - } - } -} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj b/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj index 9037420f3f..fdbe0dd150 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj @@ -28,7 +28,6 @@ - diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs index ae4eb32dff..d9eb284ac6 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs @@ -6,7 +6,10 @@ namespace Volo.Blogging { public class FileUploading { - public const int MaxFileSize = 5242880; //5MB + /// + /// Default value: 5242880 + /// + public static int MaxFileSize { get; set; } = 5242880; //5MB public static int MaxFileSizeAsMegabytes => Convert.ToInt32((MaxFileSize / 1024f) / 1024f); } diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/CreatePostDto.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/CreatePostDto.cs index 164e4f691f..96cfbd6859 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/CreatePostDto.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/CreatePostDto.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; namespace Volo.Blogging.Posts { @@ -8,22 +9,22 @@ namespace Volo.Blogging.Posts public Guid BlogId { get; set; } [Required] - [StringLength(PostConsts.MaxTitleLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxTitleLength))] public string Title { get; set; } [Required] public string CoverImage { get; set; } [Required] - [StringLength(PostConsts.MaxUrlLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxUrlLength))] public string Url { get; set; } - [StringLength(PostConsts.MaxContentLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxContentLength))] public string Content { get; set; } public string Tags { get; set; } - [StringLength(PostConsts.MaxDescriptionLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxDescriptionLength))] public string Description { get; set; } } diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Blogs/BlogConsts.cs b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Blogs/BlogConsts.cs index 502a4364e7..b4b428207c 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Blogs/BlogConsts.cs +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Blogs/BlogConsts.cs @@ -2,10 +2,19 @@ { public static class BlogConsts { - public const int MaxNameLength = 256; + /// + /// Default value: 256 + /// + public static int MaxNameLength { get; set; } = 256; - public const int MaxShortNameLength = 32; + /// + /// Default value: 32 + /// + public static int MaxShortNameLength { get; set; } = 32; - public const int MaxDescriptionLength = 1024; + /// + /// Default value: 1024 + /// + public static int MaxDescriptionLength { get; set; } = 1024; } } diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Comments/CommentConsts.cs b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Comments/CommentConsts.cs index 34cc655fb3..359b322cbf 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Comments/CommentConsts.cs +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Comments/CommentConsts.cs @@ -2,6 +2,9 @@ { public class CommentConsts { - public const int MaxTextLength = 1024; + /// + /// Default value: 1024 + /// + public static int MaxTextLength { get; set; } = 1024; } } diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs index 665984dfbb..de91caa2d3 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs @@ -2,16 +2,34 @@ { public static class PostConsts { - public const int MaxTitleLength = 512; + /// + /// Default value: 512 + /// + public static int MaxTitleLength { get; set; } = 512; - public const int MaxUrlLength = 64; + /// + /// Default value: 64 + /// + public static int MaxUrlLength { get; set; } = 64; - public const int MaxContentLength = 1024 * 1024; //1MB + /// + /// Default value: 1024 * 1024 + /// + public static int MaxContentLength { get; set; } = 1024 * 1024; //1MB - public const int MaxDescriptionLength = 1000; + /// + /// Default value: 1000 + /// + public static int MaxDescriptionLength { get; set; } = 1000; - public const int MaxTitleLengthToBeSeoFriendly = 60; + /// + /// Default value: 60 + /// + public static int MaxTitleLengthToBeSeoFriendly { get; set; } = 60; - public const int MaxSeoFriendlyDescriptionLength = 200; + /// + /// Default value: 200 + /// + public static int MaxSeoFriendlyDescriptionLength { get; set; } = 200; } } diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Tagging/TagConsts.cs b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Tagging/TagConsts.cs index c75ccd4184..ad2be87ca8 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Tagging/TagConsts.cs +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Tagging/TagConsts.cs @@ -2,8 +2,14 @@ { public class TagConsts { - public const int MaxNameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxNameLength { get; set; } = 64; - public const int MaxDescriptionLength = 512; + /// + /// Default value: 512 + /// + public static int MaxDescriptionLength { get; set; } = 512; } } diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs index c2ebcc441b..6d998a7172 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Validation; using Volo.Blogging.Blogs; using Volo.Blogging.Pages.Blogs.Shared.Helpers; using Volo.Blogging.Posts; @@ -85,7 +86,7 @@ namespace Volo.Blogging.Pages.Blog.Posts public Guid BlogId { get; set; } [Required] - [StringLength(PostConsts.MaxTitleLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxTitleLength))] public string Title { get; set; } [Required] @@ -93,14 +94,14 @@ namespace Volo.Blogging.Pages.Blog.Posts public string CoverImage { get; set; } [Required] - [StringLength(PostConsts.MaxUrlLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxUrlLength))] public string Url { get; set; } [HiddenInput] - [StringLength(PostConsts.MaxContentLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxContentLength))] public string Content { get; set; } - [StringLength(PostConsts.MaxDescriptionLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxDescriptionLength))] public string Description { get; set; } public string Tags { get; set; } diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml.cs b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml.cs index 41c8f43ddd..3930a79900 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Validation; using Volo.Blogging.Blogs; using Volo.Blogging.Blogs.Dtos; using Volo.Blogging.Pages.Blogs.Shared.Helpers; @@ -79,7 +80,7 @@ namespace Volo.Blogging.Pages.Blog.Posts public Guid BlogId { get; set; } [Required] - [StringLength(PostConsts.MaxTitleLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxTitleLength))] public string Title { get; set; } [Required] @@ -87,16 +88,16 @@ namespace Volo.Blogging.Pages.Blog.Posts public string CoverImage { get; set; } [Required] - [StringLength(PostConsts.MaxUrlLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxUrlLength))] public string Url { get; set; } [HiddenInput] - [StringLength(PostConsts.MaxContentLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxContentLength))] public string Content { get; set; } public string Tags { get; set; } - [StringLength(PostConsts.MaxDescriptionLength)] + [DynamicStringLength(typeof(PostConsts), nameof(PostConsts.MaxDescriptionLength))] public string Description { get; set; } } diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js index f5686e64e6..5e5c38d895 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js @@ -94,7 +94,7 @@ var validationResult = $form.validate(); abp.message.warn(validationResult.errorList[0].message); //TODO: errors can be merged into lines. make sweetalert accept HTML. e.preventDefault(); - return false; //for old browsers + return false; //for old browsers } $submitButton.buttonBusy(); @@ -122,6 +122,7 @@ title = title.replace('& ', ' '); title = title.replace('&', ''); title = title.replace(' ', '-'); + title = title.replace('/', '-'); title = title.replace(new RegExp(' ', 'g'), '-'); reflectedChange = true; $url.val(title); @@ -134,4 +135,4 @@ } }); -}); \ No newline at end of file +}); diff --git a/modules/blogging/src/Volo.Blogging.Web/Properties/launchSettings.json b/modules/blogging/src/Volo.Blogging.Web/Properties/launchSettings.json deleted file mode 100644 index 64a5fd912c..0000000000 --- a/modules/blogging/src/Volo.Blogging.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:50000/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Blogging.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:50014/" - } - } -} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj index e26b58d550..203c142de9 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj @@ -28,9 +28,7 @@ - - diff --git a/modules/client-simulation/src/Volo.ClientSimulation.Web/Properties/launchSettings.json b/modules/client-simulation/src/Volo.ClientSimulation.Web/Properties/launchSettings.json deleted file mode 100644 index 2f043da627..0000000000 --- a/modules/client-simulation/src/Volo.ClientSimulation.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51023/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.ClientSimulation.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:51024/" - } - } -} \ No newline at end of file diff --git a/modules/client-simulation/src/Volo.ClientSimulation.Web/Volo.ClientSimulation.Web.csproj b/modules/client-simulation/src/Volo.ClientSimulation.Web/Volo.ClientSimulation.Web.csproj index e8358805b7..aa3ecf2dac 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation.Web/Volo.ClientSimulation.Web.csproj +++ b/modules/client-simulation/src/Volo.ClientSimulation.Web/Volo.ClientSimulation.Web.csproj @@ -27,9 +27,7 @@ - - diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.Designer.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.Designer.cs similarity index 98% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.Designer.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.Designer.cs index 5fbced9e4a..2fb2a6782a 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.Designer.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.Designer.cs @@ -11,7 +11,7 @@ using VoloDocs.EntityFrameworkCore; namespace VoloDocs.EntityFrameworkCore.Migrations { [DbContext(typeof(VoloDocsDbContext))] - [Migration("20200617102723_Initial")] + [Migration("20200624023838_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -31,10 +31,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .IsRequired() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Description") .HasColumnType("nvarchar(256)") @@ -79,10 +78,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .IsRequired() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("ExtraProperties") .HasColumnName("ExtraProperties") @@ -164,7 +162,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -437,7 +436,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -594,7 +594,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Content") .IsRequired() @@ -692,7 +693,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("DefaultDocumentName") .IsRequired() diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.cs similarity index 98% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.cs index 179387743c..66b5265a33 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.cs @@ -13,7 +13,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations { Id = table.Column(nullable: false), ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(maxLength: 256, nullable: false), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), Name = table.Column(maxLength: 256, nullable: false), Required = table.Column(nullable: false), IsStatic = table.Column(nullable: false), @@ -33,7 +33,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations { Id = table.Column(nullable: false), ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), CreationTime = table.Column(nullable: false), CreatorId = table.Column(nullable: true), LastModificationTime = table.Column(nullable: true), @@ -78,7 +78,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations { Id = table.Column(nullable: false), ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(maxLength: 256, nullable: false), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), TenantId = table.Column(nullable: true), Name = table.Column(maxLength: 256, nullable: false), NormalizedName = table.Column(maxLength: 256, nullable: false), @@ -112,7 +112,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations { Id = table.Column(nullable: false), ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), CreationTime = table.Column(nullable: false), CreatorId = table.Column(nullable: true), LastModificationTime = table.Column(nullable: true), @@ -148,7 +148,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations { Id = table.Column(nullable: false), ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), ProjectId = table.Column(nullable: false), Name = table.Column(maxLength: 255, nullable: false), Version = table.Column(maxLength: 128, nullable: false), @@ -176,7 +176,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations { Id = table.Column(nullable: false), ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), Name = table.Column(maxLength: 128, nullable: false), ShortName = table.Column(maxLength: 32, nullable: false), Format = table.Column(nullable: true), diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs index 43feaed625..73b261e454 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs @@ -29,10 +29,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .IsRequired() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Description") .HasColumnType("nvarchar(256)") @@ -77,10 +76,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .IsRequired() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("ExtraProperties") .HasColumnName("ExtraProperties") @@ -162,7 +160,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -435,7 +434,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -592,7 +592,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Content") .IsRequired() @@ -690,7 +691,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("DefaultDocumentName") .IsRequired() diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs index e375f6ba0b..1512c13da9 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using Volo.Abp.Application.Dtos; +using Volo.Abp.Validation; using Volo.Docs.Documents; namespace Volo.Docs.Admin.Documents @@ -9,19 +10,19 @@ namespace Volo.Docs.Admin.Documents { public Guid? ProjectId { get; set; } - [StringLength(DocumentConsts.MaxNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxNameLength))] public string Name { get; set; } - [StringLength(DocumentConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxVersionNameLength))] public string Version { get; set; } - [StringLength(DocumentConsts.MaxLanguageCodeNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxLanguageCodeNameLength))] public string LanguageCode { get; set; } - [StringLength(DocumentConsts.MaxFileNameNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxFileNameNameLength))] public string FileName { get; set; } - [StringLength(DocumentConsts.MaxFormatNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxFormatNameLength))] public string Format { get; set; } public DateTime? CreationTimeMin { get; set; } diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/PullAllDocumentInput.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/PullAllDocumentInput.cs index d796c00e64..c375de8b67 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/PullAllDocumentInput.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/PullAllDocumentInput.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; using Volo.Docs.Documents; namespace Volo.Docs.Admin.Documents @@ -8,10 +9,10 @@ namespace Volo.Docs.Admin.Documents { public Guid ProjectId { get; set; } - [StringLength(DocumentConsts.MaxLanguageCodeNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxLanguageCodeNameLength))] public string LanguageCode { get; set; } - [StringLength(DocumentConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxVersionNameLength))] public string Version { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/PullDocumentInput.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/PullDocumentInput.cs index fcc9816b02..dce5de0420 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/PullDocumentInput.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/PullDocumentInput.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; using Volo.Docs.Documents; namespace Volo.Docs.Admin.Documents @@ -8,13 +9,13 @@ namespace Volo.Docs.Admin.Documents { public Guid ProjectId { get; set; } - [StringLength(DocumentConsts.MaxNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxNameLength))] public string Name { get; set; } - [StringLength(DocumentConsts.MaxLanguageCodeNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxLanguageCodeNameLength))] public string LanguageCode { get; set; } - [StringLength(DocumentConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxVersionNameLength))] public string Version { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.csproj b/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.csproj index 076bc1d37a..00b0e26d75 100644 --- a/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.csproj +++ b/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.csproj @@ -4,7 +4,7 @@ - netcoreapp3.1 + netstandard2.0 Volo.Docs.Admin.HttpApi.Client Volo.Docs.Admin.HttpApi.Client diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs index e6de68a3b1..506b674ba5 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Volo.Abp; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Validation; using Volo.Docs.Admin.Projects; using Volo.Docs.Projects; @@ -70,11 +71,11 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects public abstract class CreateProjectViewModelBase { [Required] - [StringLength(ProjectConsts.MaxNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxNameLength))] public string Name { get; set; } [Required] - [StringLength(ProjectConsts.MaxShortNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxShortNameLength))] [InputInfoText("ShortNameInfoText")] public string ShortName { get; set; } @@ -82,21 +83,21 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects [SelectItems(nameof(FormatTypes))] public string Format { get; set; } - [StringLength(ProjectConsts.MaxDefaultDocumentNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxDefaultDocumentNameLength))] public string DefaultDocumentName { get; set; } = "Index"; - [StringLength(ProjectConsts.MaxNavigationDocumentNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxNavigationDocumentNameLength))] public string NavigationDocumentName { get; set; } = "docs-nav.json"; - [StringLength(ProjectConsts.MaxParametersDocumentNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxParametersDocumentNameLength))] public string ParametersDocumentName { get; set; } = "docs-params.json"; - [StringLength(ProjectConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] public string MinimumVersion { get; set; } public string MainWebsiteUrl { get; set; } - [StringLength(ProjectConsts.MaxLatestVersionBranchNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxLatestVersionBranchNameLength))] public string LatestVersionBranchName { get; set; } [HiddenInput] diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs index 3b9aba5303..ef1762c460 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Volo.Abp; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Validation; using Volo.Docs.Admin.Projects; using Volo.Docs.Projects; @@ -83,28 +84,28 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects public Guid Id { get; set; } [Required] - [StringLength(ProjectConsts.MaxNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxNameLength))] public string Name { get; set; } [Required] [SelectItems(nameof(FormatTypes))] public string Format { get; set; } - [StringLength(ProjectConsts.MaxDefaultDocumentNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxDefaultDocumentNameLength))] public string DefaultDocumentName { get; set; } - [StringLength(ProjectConsts.MaxNavigationDocumentNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxNavigationDocumentNameLength))] public string NavigationDocumentName { get; set; } - [StringLength(ProjectConsts.MaxParametersDocumentNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxParametersDocumentNameLength))] public string ParametersDocumentName { get; set; } - [StringLength(ProjectConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] public string MinimumVersion { get; set; } public string MainWebsiteUrl { get; set; } - [StringLength(ProjectConsts.MaxLatestVersionBranchNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxLatestVersionBranchNameLength))] public string LatestVersionBranchName { get; set; } } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml.cs index 195337a22e..5cbd094834 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Validation; using Volo.Docs.Admin.Documents; using Volo.Docs.Admin.Projects; using Volo.Docs.Documents; @@ -60,15 +61,15 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects public bool All { get; set; } [Required] - [StringLength(DocumentConsts.MaxNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxNameLength))] public string Name { get; set; } [Required] - [StringLength(DocumentConsts.MaxLanguageCodeNameLength)] + [DynamicStringLength(typeof(DocumentConsts),nameof(DocumentConsts.MaxLanguageCodeNameLength))] public string LanguageCode { get; set; } [Required] - [StringLength(DocumentConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxVersionNameLength))] public string Version { get; set; } } } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Properties/launchSettings.json b/modules/docs/src/Volo.Docs.Admin.Web/Properties/launchSettings.json deleted file mode 100644 index b0ed7371d0..0000000000 --- a/modules/docs/src/Volo.Docs.Admin.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51397", - "sslPort": 44346 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Docs.Admin.Web": { - "commandName": "Project", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj index 164c28cdb2..5b75a24f41 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj +++ b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj @@ -30,11 +30,9 @@ - - diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDefaultDocumentInput.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDefaultDocumentInput.cs index 3deeb83967..1cef038512 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDefaultDocumentInput.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDefaultDocumentInput.cs @@ -1,5 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Runtime.CompilerServices; +using Volo.Abp.Validation; using Volo.Docs.Language; using Volo.Docs.Projects; @@ -9,11 +11,11 @@ namespace Volo.Docs.Documents { public Guid ProjectId { get; set; } - [StringLength(ProjectConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] public string Version { get; set; } [Required] - [StringLength(LanguageConsts.MaxLanguageCodeLength)] + [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxLanguageCodeLength))] public string LanguageCode { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentInput.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentInput.cs index 412f41b82e..3646f2ade0 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentInput.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentInput.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; using Volo.Docs.Language; using Volo.Docs.Projects; @@ -10,14 +11,14 @@ namespace Volo.Docs.Documents public Guid ProjectId { get; set; } [Required] - [StringLength(DocumentConsts.MaxNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxNameLength))] public string Name { get; set; } - [StringLength(ProjectConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] public string Version { get; set; } [Required] - [StringLength(LanguageConsts.MaxLanguageCodeLength)] + [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxLanguageCodeLength))] public string LanguageCode { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentResourceInput.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentResourceInput.cs index 6f759c47d8..52c1cf78af 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentResourceInput.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentResourceInput.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; using Volo.Docs.Language; using Volo.Docs.Projects; @@ -10,14 +11,14 @@ namespace Volo.Docs.Documents public Guid ProjectId { get; set; } [Required] - [StringLength(DocumentConsts.MaxNameLength)] + [DynamicStringLength(typeof(DocumentConsts), nameof(DocumentConsts.MaxNameLength))] public string Name { get; set; } - [StringLength(ProjectConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] public string Version { get; set; } [Required] - [StringLength(LanguageConsts.MaxLanguageCodeLength)] + [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxLanguageCodeLength))] public string LanguageCode { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetNavigationDocumentInput.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetNavigationDocumentInput.cs index 6866c8c633..ee4e3380ee 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetNavigationDocumentInput.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetNavigationDocumentInput.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; using Volo.Docs.Language; using Volo.Docs.Projects; @@ -9,11 +10,11 @@ namespace Volo.Docs.Documents { public Guid ProjectId { get; set; } - [StringLength(ProjectConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] public string Version { get; set; } [Required] - [StringLength(LanguageConsts.MaxLanguageCodeLength)] + [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxLanguageCodeLength))] public string LanguageCode { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetParametersDocumentInput.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetParametersDocumentInput.cs index 33bd25b394..1e277f36a3 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetParametersDocumentInput.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetParametersDocumentInput.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; using Volo.Docs.Language; using Volo.Docs.Projects; @@ -9,11 +10,11 @@ namespace Volo.Docs.Documents { public Guid ProjectId { get; set; } - [StringLength(ProjectConsts.MaxVersionNameLength)] + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] public string Version { get; set; } [Required] - [StringLength(LanguageConsts.MaxLanguageCodeLength)] + [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxLanguageCodeLength))] public string LanguageCode { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentConsts.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentConsts.cs index f629abd0a4..04e3de3fd5 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentConsts.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentConsts.cs @@ -2,14 +2,49 @@ { public static class DocumentConsts { - public const int MaxNameLength = 255; - public const int MaxVersionNameLength = 128; - public const int MaxLanguageCodeNameLength = 128; - public const int MaxFileNameNameLength = 128; - public const int MaxFormatNameLength = 128; - public const int MaxEditLinkLength = 2048; - public const int MaxRootUrlLength = 2048; - public const int MaxRawRootUrlLength = 2048; - public const int MaxLocalDirectoryLength = 512; + /// + /// Default value: 255 + /// + public static int MaxNameLength { get; set; } = 255; + + /// + /// Default value: 128 + /// + public static int MaxVersionNameLength { get; set; } = 128; + + /// + /// Default value: 128 + /// + public static int MaxLanguageCodeNameLength { get; set; } = 128; + + /// + /// Default value: 128 + /// + public static int MaxFileNameNameLength { get; set; } = 128; + + /// + /// Default value: 128 + /// + public static int MaxFormatNameLength { get; set; } = 128; + + /// + /// Default value: 2048 + /// + public static int MaxEditLinkLength { get; set; } = 2048; + + /// + /// Default value: 2048 + /// + public static int MaxRootUrlLength { get; set; } = 2048; + + /// + /// Default value: 2048 + /// + public static int MaxRawRootUrlLength { get; set; } = 2048; + + /// + /// Default value: 512 + /// + public static int MaxLocalDirectoryLength { get; set; } = 512; } } diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Language/LanguageConsts.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Language/LanguageConsts.cs index 867cc2dd4e..cd449a37fd 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Language/LanguageConsts.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Language/LanguageConsts.cs @@ -3,6 +3,9 @@ namespace Volo.Docs.Language { public static class LanguageConsts { - public const int MaxLanguageCodeLength = 10; + /// + /// Default value: 10 + /// + public static int MaxLanguageCodeLength { get; set; } = 10; } } diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Projects/ProjectConsts.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Projects/ProjectConsts.cs index 927a1a4c59..3322fb7732 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Projects/ProjectConsts.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Projects/ProjectConsts.cs @@ -2,12 +2,39 @@ { public static class ProjectConsts { - public const int MaxNameLength = 128; - public const int MaxShortNameLength = 32; - public const int MaxDefaultDocumentNameLength = 128; - public const int MaxNavigationDocumentNameLength = 128; - public const int MaxParametersDocumentNameLength = 128; - public const int MaxLatestVersionBranchNameLength = 128; - public const int MaxVersionNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxNameLength { get; set; } = 128; + + /// + /// Default value: 32 + /// + public static int MaxShortNameLength { get; set; } = 32; + + /// + /// Default value: 128 + /// + public static int MaxDefaultDocumentNameLength { get; set; } = 128; + + /// + /// Default value: 128 + /// + public static int MaxNavigationDocumentNameLength { get; set; } = 128; + + /// + /// Default value: 128 + /// + public static int MaxParametersDocumentNameLength { get; set; } = 128; + + /// + /// Default value: 128 + /// + public static int MaxLatestVersionBranchNameLength { get; set; } = 128; + + /// + /// Default value: 128 + /// + public static int MaxVersionNameLength { get; set; } = 128; } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs index ef6f6e1ebb..1da1ab1f9d 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs @@ -38,7 +38,7 @@ namespace Volo.Docs.GitHub.Documents var rawDocumentUrl = rawRootUrl + documentName; var isNavigationDocument = documentName == project.NavigationDocumentName; var isParameterDocument = documentName == project.ParametersDocumentName; - var editLink = rootUrl.ReplaceFirst("/tree/", "/blob/") + languageCode + "/" + documentName; + var editLink = rootUrl.ReplaceFirst("/tree/", "/blob/").EnsureEndsWith('/') + languageCode + "/" + documentName; var localDirectory = ""; var fileName = documentName; diff --git a/modules/docs/src/Volo.Docs.Web/Properties/launchSettings.json b/modules/docs/src/Volo.Docs.Web/Properties/launchSettings.json deleted file mode 100644 index e32f163034..0000000000 --- a/modules/docs/src/Volo.Docs.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55007/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Docs.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:55020/" - } - } -} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj b/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj index 9afc79d796..4479ec40af 100644 --- a/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj +++ b/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj @@ -33,11 +33,9 @@ - - diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureManagementRemoteServiceConsts.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureManagementRemoteServiceConsts.cs index 749805da05..c941a1b7eb 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureManagementRemoteServiceConsts.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureManagementRemoteServiceConsts.cs @@ -2,6 +2,6 @@ { public class FeatureManagementRemoteServiceConsts { - public const string RemoteServiceName = "FeatureManagement"; + public const string RemoteServiceName = "AbpFeatureManagement"; } } \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs index 24b04b7013..6c9db9bade 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs @@ -16,7 +16,6 @@ namespace Volo.Abp.FeatureManagement public class FeatureAppService : FeatureManagementAppServiceBase, IFeatureAppService { protected FeatureManagementOptions Options { get; } - protected IFeatureManager FeatureManager { get; } protected IFeatureDefinitionManager FeatureDefinitionManager { get; } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs index b35225edb2..26388fdaf9 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs @@ -1,6 +1,9 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; using Volo.Abp.Guids; namespace Volo.Abp.FeatureManagement @@ -8,17 +11,20 @@ namespace Volo.Abp.FeatureManagement public class FeatureManagementStore : IFeatureManagementStore, ITransientDependency { protected IDistributedCache Cache { get; } + protected IFeatureDefinitionManager FeatureDefinitionManager { get; } protected IFeatureValueRepository FeatureValueRepository { get; } protected IGuidGenerator GuidGenerator { get; } public FeatureManagementStore( IFeatureValueRepository featureValueRepository, IGuidGenerator guidGenerator, - IDistributedCache cache) + IDistributedCache cache, + IFeatureDefinitionManager featureDefinitionManager) { FeatureValueRepository = featureValueRepository; GuidGenerator = guidGenerator; Cache = cache; + FeatureDefinitionManager = featureDefinitionManager; } public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey) @@ -61,16 +67,43 @@ namespace Volo.Abp.FeatureManagement return cacheItem; } - var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey); + cacheItem = new FeatureValueCacheItem(null); + + await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); - cacheItem = new FeatureValueCacheItem(featureValue?.Value); + return cacheItem; + } + + private async Task SetCacheItemsAsync( + string providerName, + string providerKey, + string currentName, + FeatureValueCacheItem currentCacheItem) + { + var featureDefinitions = FeatureDefinitionManager.GetAll(); + var featuresDictionary = (await FeatureValueRepository.GetListAsync(providerName, providerKey)) + .ToDictionary(s => s.Name, s => s.Value); + + var cacheItems = new List>(); + + foreach (var featureDefinition in featureDefinitions) + { + var featureValue = featuresDictionary.GetOrDefault(featureDefinition.Name); + + cacheItems.Add( + new KeyValuePair( + CalculateCacheKey(featureDefinition.Name, providerName, providerKey), + new FeatureValueCacheItem(featureValue) + ) + ); - await Cache.SetAsync( - cacheKey, - cacheItem - ); + if (featureDefinition.Name == currentName) + { + currentCacheItem.Value = featureValue; + } + } - return cacheItem; + await Cache.SetManyAsync(cacheItems); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureValueConsts.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureValueConsts.cs index 0fd1a644fa..f44deca44a 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureValueConsts.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureValueConsts.cs @@ -2,12 +2,24 @@ { public static class FeatureValueConsts { - public const int MaxNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxNameLength { get; set;} = 128; - public const int MaxProviderNameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxProviderNameLength { get; set;} = 64; - public const int MaxProviderKeyLength = 64; + /// + /// Default value: 64 + /// + public static int MaxProviderKeyLength { get; set;} = 64; - public const int MaxValueLength = 128; + /// + /// Default value: 128 + /// + public static int MaxValueLength { get; set;} = 128; } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs index e3eda46033..0ae78ed03f 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs @@ -5,7 +5,8 @@ using Volo.Abp.AspNetCore.Mvc; namespace Volo.Abp.FeatureManagement { [RemoteService(Name = FeatureManagementRemoteServiceConsts.RemoteServiceName)] - [Area("abp")] + [Area("featureManagement")] + [Route("api/feature-management/features")] public class FeaturesController : AbpController, IFeatureAppService { protected IFeatureAppService FeatureAppService { get; } @@ -15,11 +16,13 @@ namespace Volo.Abp.FeatureManagement FeatureAppService = featureAppService; } + [HttpGet] public virtual Task GetAsync(string providerName, string providerKey) { return FeatureAppService.GetAsync(providerName, providerKey); } + [HttpPut] public virtual Task UpdateAsync(string providerName, string providerKey, UpdateFeaturesDto input) { return FeatureAppService.UpdateAsync(providerName, providerKey, input); diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Properties/launchSettings.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Properties/launchSettings.json deleted file mode 100644 index 23a4efbb61..0000000000 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:56993/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Abp.FeatureManagement.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:56994/" - } - } -} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs index 8e11d89b30..491e82d766 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs @@ -1,12 +1,13 @@ using System.ComponentModel.DataAnnotations; using Volo.Abp.ObjectExtending; +using Volo.Abp.Validation; namespace Volo.Abp.Identity { public class IdentityRoleCreateOrUpdateDtoBase : ExtensibleObject { [Required] - [StringLength(IdentityRoleConsts.MaxNameLength)] + [DynamicStringLength(typeof(IdentityRoleConsts), nameof(IdentityRoleConsts.MaxNameLength))] public string Name { get; set; } public bool IsDefault { get; set; } diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateDto.cs index a572c7db2c..a080a7e33e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateDto.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateDto.cs @@ -1,13 +1,14 @@ using System.ComponentModel.DataAnnotations; using Volo.Abp.Auditing; +using Volo.Abp.Validation; namespace Volo.Abp.Identity { public class IdentityUserCreateDto : IdentityUserCreateOrUpdateDtoBase { - [Required] - [StringLength(IdentityUserConsts.MaxPasswordLength)] [DisableAuditing] + [Required] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] public string Password { get; set; } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs index c18f2c16b5..e06ea8b987 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs @@ -2,27 +2,28 @@ using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; using Volo.Abp.ObjectExtending; +using Volo.Abp.Validation; namespace Volo.Abp.Identity { public abstract class IdentityUserCreateOrUpdateDtoBase : ExtensibleObject { [Required] - [StringLength(IdentityUserConsts.MaxUserNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] public string UserName { get; set; } - [StringLength(IdentityUserConsts.MaxNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))] public string Name { get; set; } - [StringLength(IdentityUserConsts.MaxSurnameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))] public string Surname { get; set; } [Required] [EmailAddress] - [StringLength(IdentityUserConsts.MaxEmailLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] public string Email { get; set; } - [StringLength(IdentityUserConsts.MaxPhoneNumberLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))] public string PhoneNumber { get; set; } public bool TwoFactorEnabled { get; set; } diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateDto.cs index 216a55df9e..aa499bf2cd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateDto.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateDto.cs @@ -1,13 +1,14 @@ using System.ComponentModel.DataAnnotations; using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; +using Volo.Abp.Validation; namespace Volo.Abp.Identity { public class IdentityUserUpdateDto : IdentityUserCreateOrUpdateDtoBase, IHasConcurrencyStamp { - [StringLength(IdentityUserConsts.MaxPasswordLength)] [DisableAuditing] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] public string Password { get; set; } public string ConcurrencyStamp { get; set; } diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UpdateProfileDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UpdateProfileDto.cs index 52919c7744..5e0397bdfa 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UpdateProfileDto.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UpdateProfileDto.cs @@ -1,23 +1,24 @@ using System.ComponentModel.DataAnnotations; using Volo.Abp.ObjectExtending; +using Volo.Abp.Validation; namespace Volo.Abp.Identity { public class UpdateProfileDto : ExtensibleObject { - [StringLength(IdentityUserConsts.MaxUserNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] public string UserName { get; set; } - [StringLength(IdentityUserConsts.MaxEmailLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] public string Email { get; set; } - [StringLength(IdentityUserConsts.MaxNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))] public string Name { get; set; } - [StringLength(IdentityUserConsts.MaxSurnameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))] public string Surname { get; set; } - [StringLength(IdentityUserConsts.MaxPhoneNumberLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))] public string PhoneNumber { get; set; } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Properties/launchSettings.json b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Properties/launchSettings.json deleted file mode 100644 index fc5b1a72f7..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53901/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Identity.AspNetCore": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53902/" - } - } -} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimTypeConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimTypeConsts.cs index 32e3197e12..eafd0ed135 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimTypeConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimTypeConsts.cs @@ -2,10 +2,24 @@ { public class IdentityClaimTypeConsts { - public const int MaxNameLength = 256; - public const int MaxRegexLength = 512; - public const int MaxRegexDescriptionLength = 128; - public const int MaxDescriptionLength = 256; - public const int MaxConcurrencyStampLength = 256; + /// + /// Default value: 256 + /// + public static int MaxNameLength { get; set; } = 256; + + /// + /// Default value: 512 + /// + public static int MaxRegexLength { get; set; } = 512; + + /// + /// Default value: 128 + /// + public static int MaxRegexDescriptionLength { get; set; } = 128; + + /// + /// Default value: 256 + /// + public static int MaxDescriptionLength { get; set; } = 256; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleClaimConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleClaimConsts.cs index f53717486f..0f20ac40ef 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleClaimConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleClaimConsts.cs @@ -2,8 +2,8 @@ { public static class IdentityRoleClaimConsts { - public const int MaxClaimTypeLength = IdentityUserClaimConsts.MaxClaimTypeLength; + public static int MaxClaimTypeLength { get; set; } = IdentityUserClaimConsts.MaxClaimTypeLength; - public const int MaxClaimValueLength = IdentityUserClaimConsts.MaxClaimValueLength; + public static int MaxClaimValueLength { get; set; } = IdentityUserClaimConsts.MaxClaimValueLength; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleConsts.cs index cac33aa53d..f51be5da8f 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleConsts.cs @@ -2,8 +2,14 @@ { public static class IdentityRoleConsts { - public const int MaxNameLength = 256; - public const int MaxNormalizedNameLength = MaxNameLength; - public const int MaxConcurrencyStampLength = 256; + /// + /// Default value: 256 + /// + public static int MaxNameLength { get; set; } = 256; + + /// + /// Default value: 256 + /// + public static int MaxNormalizedNameLength { get; set; } = 256; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserClaimConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserClaimConsts.cs index 025b255f11..84ba493853 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserClaimConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserClaimConsts.cs @@ -2,8 +2,14 @@ { public static class IdentityUserClaimConsts { - public const int MaxClaimTypeLength = 256; + /// + /// Default value: 256 + /// + public static int MaxClaimTypeLength { get; set; } = 256; - public const int MaxClaimValueLength = 1024; + /// + /// Default value: 1024 + /// + public static int MaxClaimValueLength { get; set; } = 1024; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs index ef2cc57ef4..c374cbc678 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs @@ -4,26 +4,33 @@ namespace Volo.Abp.Identity { public static class IdentityUserConsts { - public const int MaxUserNameLength = AbpUserConsts.MaxUserNameLength; + public static int MaxUserNameLength { get; set; } = AbpUserConsts.MaxUserNameLength; - public const int MaxNameLength = AbpUserConsts.MaxNameLength; + public static int MaxNameLength { get; set; } = AbpUserConsts.MaxNameLength; - public const int MaxSurnameLength = AbpUserConsts.MaxSurnameLength; + public static int MaxSurnameLength { get; set; } = AbpUserConsts.MaxSurnameLength; - public const int MaxNormalizedUserNameLength = MaxUserNameLength; + public static int MaxNormalizedUserNameLength { get; set; } = MaxUserNameLength; - public const int MaxEmailLength = AbpUserConsts.MaxEmailLength; + public static int MaxEmailLength { get; set; } = AbpUserConsts.MaxEmailLength; - public const int MaxNormalizedEmailLength = MaxEmailLength; + public static int MaxNormalizedEmailLength { get; set; } = MaxEmailLength; - public const int MaxPhoneNumberLength = AbpUserConsts.MaxPhoneNumberLength; + public static int MaxPhoneNumberLength { get; set; } = AbpUserConsts.MaxPhoneNumberLength; - public const int MaxPasswordLength = 128; + /// + /// Default value: 128 + /// + public static int MaxPasswordLength { get; set; } = 128; - public const int MaxPasswordHashLength = 256; + /// + /// Default value: 256 + /// + public static int MaxPasswordHashLength { get; set; } = 256; - public const int MaxSecurityStampLength = 256; - - public const int MaxConcurrencyStampLength = 256; + /// + /// Default value: 256 + /// + public static int MaxSecurityStampLength { get; set; } = 256; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserLoginConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserLoginConsts.cs index 0ef43bb1b1..8435221fd3 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserLoginConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserLoginConsts.cs @@ -2,8 +2,19 @@ { public static class IdentityUserLoginConsts { - public const int MaxLoginProviderLength = 64; - public const int MaxProviderKeyLength = 196; - public const int MaxProviderDisplayNameLength = 128; + /// + /// Default value: 64 + /// + public static int MaxLoginProviderLength { get; set; } = 64; + + /// + /// Default value: 196 + /// + public static int MaxProviderKeyLength { get; set; } = 196; + + /// + /// Default value: 128 + /// + public static int MaxProviderDisplayNameLength { get; set; } = 128; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserTokenConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserTokenConsts.cs index 92305851fb..a585a8383b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserTokenConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserTokenConsts.cs @@ -2,8 +2,14 @@ { public static class IdentityUserTokenConsts { - public const int MaxLoginProviderLength = 64; + /// + /// Default value: 64 + /// + public static int MaxLoginProviderLength { get; set; } = 64; - public const int MaxNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxNameLength { get; set; } = 128; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitConsts.cs index ad6091a44e..7338fc8a28 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitConsts.cs @@ -5,7 +5,7 @@ /// /// Maximum length of the property. /// - public const int MaxDisplayNameLength = 128; + public static int MaxDisplayNameLength { get; set; } = 128; /// /// Maximum depth of an OU hierarchy. diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitEto.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitEto.cs new file mode 100644 index 0000000000..09013aa1ce --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitEto.cs @@ -0,0 +1,16 @@ +using System; + +namespace Volo.Abp.Identity +{ + [Serializable] + public class OrganizationUnitEto + { + public Guid Id { get; set; } + + public Guid? TenantId { get; set; } + + public string Code { get; set; } + + public string DisplayName { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConfiguration.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConfiguration.cs index 3892fb00f6..a8e488871a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConfiguration.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConfiguration.cs @@ -31,5 +31,14 @@ namespace Volo.Abp.ObjectExtending configureAction ); } + + public IdentityModuleExtensionConfiguration ConfigureOrganizationUnit( + Action configureAction) + { + return this.ConfigureEntity( + IdentityModuleExtensionConsts.EntityNames.OrganizationUnit, + configureAction + ); + } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConsts.cs index 6d2b4c0653..45c9db6462 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConsts.cs @@ -11,6 +11,8 @@ public const string Role = "Role"; public const string ClaimType = "ClaimType"; + + public const string OrganizationUnit = "OrganizationUnit"; } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index 07c4d2e971..d21e945f60 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -34,6 +34,7 @@ namespace Volo.Abp.Identity options.EtoMappings.Add(typeof(AbpIdentityDomainModule)); options.EtoMappings.Add(typeof(AbpIdentityDomainModule)); options.EtoMappings.Add(typeof(AbpIdentityDomainModule)); + options.EtoMappings.Add(typeof(AbpIdentityDomainModule)); }); var identityBuilder = context.Services.AddAbpIdentity(options => @@ -66,6 +67,12 @@ namespace Volo.Abp.Identity IdentityModuleExtensionConsts.EntityNames.ClaimType, typeof(IdentityClaimType) ); + + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.OrganizationUnit, + typeof(OrganizationUnit) + ); } private static void AddAbpIdentityOptionsFactory(IServiceCollection services) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs index 547525b46e..9a5557401d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs @@ -10,6 +10,7 @@ namespace Volo.Abp.Identity CreateMap(); CreateMap(); CreateMap(); + CreateMap(); } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs index 74cc2ce995..000a2a8f00 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -126,9 +126,6 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.Property(r => r.Name).IsRequired().HasMaxLength(IdentityRoleConsts.MaxNameLength); b.Property(r => r.NormalizedName).IsRequired().HasMaxLength(IdentityRoleConsts.MaxNormalizedNameLength); - b.Property(u => u.ConcurrencyStamp).IsRequired().IsConcurrencyToken() - .HasMaxLength(IdentityRoleConsts.MaxConcurrencyStampLength) - .HasColumnName(nameof(IdentityRole.ConcurrencyStamp)); b.Property(r => r.IsDefault).HasColumnName(nameof(IdentityRole.IsDefault)); b.Property(r => r.IsStatic).HasColumnName(nameof(IdentityRole.IsStatic)); b.Property(r => r.IsPublic).HasColumnName(nameof(IdentityRole.IsPublic)); @@ -163,9 +160,6 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.Property(uc => uc.Regex).HasMaxLength(IdentityClaimTypeConsts.MaxRegexLength); b.Property(uc => uc.RegexDescription).HasMaxLength(IdentityClaimTypeConsts.MaxRegexDescriptionLength); b.Property(uc => uc.Description).HasMaxLength(IdentityClaimTypeConsts.MaxDescriptionLength); - b.Property(uc => uc.ConcurrencyStamp).IsRequired().IsConcurrencyToken() - .HasMaxLength(IdentityClaimTypeConsts.MaxConcurrencyStampLength) - .HasColumnName(nameof(IdentityClaimType.ConcurrencyStamp)); }); builder.Entity(b => diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Properties/launchSettings.json b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Properties/launchSettings.json deleted file mode 100644 index 6ba85345ab..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53978/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Identity.HttpApi.Client": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53980/" - } - } -} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Properties/launchSettings.json b/modules/identity/src/Volo.Abp.Identity.HttpApi/Properties/launchSettings.json deleted file mode 100644 index 2323768a18..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53977/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Identity.HttpApi": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53979/" - } - } -} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs index e7244c80f1..df86acd94d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Validation; namespace Volo.Abp.Identity.Web.Pages.Identity.Roles { @@ -34,7 +35,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Roles public class RoleInfoModel { [Required] - [StringLength(IdentityRoleConsts.MaxNameLength)] + [DynamicStringLength(typeof(IdentityRoleConsts), nameof(IdentityRoleConsts.MaxNameLength))] [Display(Name = "DisplayName:RoleName")] public string Name { get; set; } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs index 69305d243c..8a004e95a5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Domain.Entities; +using Volo.Abp.Validation; namespace Volo.Abp.Identity.Web.Pages.Identity.Roles { @@ -44,7 +45,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Roles public string ConcurrencyStamp { get; set; } [Required] - [StringLength(IdentityRoleConsts.MaxNameLength)] + [DynamicStringLength(typeof(IdentityRoleConsts), nameof(IdentityRoleConsts.MaxNameLength))] [Display(Name = "DisplayName:RoleName")] public string Name { get; set; } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs index e7a1f4a26d..6149c676b8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Auditing; using Volo.Abp.Application.Dtos; +using Volo.Abp.Validation; namespace Volo.Abp.Identity.Web.Pages.Identity.Users { @@ -54,27 +55,27 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users public class UserInfoViewModel { [Required] - [StringLength(IdentityUserConsts.MaxUserNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] public string UserName { get; set; } - [StringLength(IdentityUserConsts.MaxNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))] public string Name { get; set; } - [StringLength(IdentityUserConsts.MaxSurnameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))] public string Surname { get; set; } [Required] - [StringLength(IdentityUserConsts.MaxPasswordLength)] [DataType(DataType.Password)] [DisableAuditing] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] public string Password { get; set; } [Required] [EmailAddress] - [StringLength(IdentityUserConsts.MaxEmailLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] public string Email { get; set; } - [StringLength(IdentityUserConsts.MaxPhoneNumberLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))] public string PhoneNumber { get; set; } public bool TwoFactorEnabled { get; set; } = true; diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs index f14ec21fe6..08c5ae0dd9 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Volo.Abp.Auditing; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; +using Volo.Abp.Validation; namespace Volo.Abp.Identity.Web.Pages.Identity.Users { @@ -63,26 +64,26 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users public string ConcurrencyStamp { get; set; } [Required] - [StringLength(IdentityUserConsts.MaxUserNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] public string UserName { get; set; } - [StringLength(IdentityUserConsts.MaxNameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))] public string Name { get; set; } - [StringLength(IdentityUserConsts.MaxSurnameLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))] public string Surname { get; set; } - [StringLength(IdentityUserConsts.MaxPasswordLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] [DataType(DataType.Password)] [DisableAuditing] public string Password { get; set; } [Required] [EmailAddress] - [StringLength(IdentityUserConsts.MaxEmailLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] public string Email { get; set; } - [StringLength(IdentityUserConsts.MaxPhoneNumberLength)] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))] public string PhoneNumber { get; set; } public bool TwoFactorEnabled { get; set; } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Properties/launchSettings.json b/modules/identity/src/Volo.Abp.Identity.Web/Properties/launchSettings.json deleted file mode 100644 index 9a8b023b55..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55588/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.Identity.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:55590/" - } - } -} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj index f5c9ffc3d4..5a8367dc5f 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -25,9 +25,7 @@ - - diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientIdPRestrictionConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientIdPRestrictionConsts.cs index c71301871a..b238a01935 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientIdPRestrictionConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientIdPRestrictionConsts.cs @@ -2,6 +2,9 @@ { public class ClientIdPRestrictionConsts { - public const int ProviderMaxLength = 200; + /// + /// Default value: 200 + /// + public static int ProviderMaxLength { get; set; } = 200; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs index 3092232d47..7d4764e1a3 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs @@ -2,7 +2,10 @@ { public class ClientPostLogoutRedirectUriConsts { - public const int PostLogoutRedirectUriMaxLength = 2000; + /// + /// Default value: 200 + /// + public static int PostLogoutRedirectUriMaxLength { get; set; } = 2000; public static int PostLogoutRedirectUriMaxLengthValue { get; set; } = 2000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPropertyConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPropertyConsts.cs index 6535304596..664e1076b0 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPropertyConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPropertyConsts.cs @@ -2,7 +2,14 @@ { public class ClientPropertyConsts { - public const int KeyMaxLength = 250; - public const int ValueMaxLength = 2000; + /// + /// Default value: 250 + /// + public static int KeyMaxLength { get; set; } = 250; + + /// + /// Default value: 2000 + /// + public static int ValueMaxLength { get; set; } = 2000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs index e972388487..7650a3d952 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs @@ -2,7 +2,10 @@ { public class ClientRedirectUriConsts { - public const int RedirectUriMaxLength = 2000; + /// + /// Default value: 2000 + /// + public static int RedirectUriMaxLength { get; set; } = 2000; public static int RedirectUriMaxLengthValue { get; set; } = 2000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientScopeConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientScopeConsts.cs index 6a5b62e692..0ec1c04595 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientScopeConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientScopeConsts.cs @@ -2,6 +2,9 @@ { public class ClientScopeConsts { - public const int ScopeMaxLength = 200; + /// + /// Default value: 200 + /// + public static int ScopeMaxLength { get; set; } = 200; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs index 6d0836ab17..b1dd21d6f8 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs @@ -2,11 +2,34 @@ { public class PersistedGrantConsts { - public const int KeyMaxLength = 200; - public const int TypeMaxLength = 50; - public const int SubjectIdMaxLength = 200; - public const int ClientIdMaxLength = 200; - public const int DataMaxLength = 50000; - public static int DataMaxLengthValue { get; set; }= 50000; + /// + /// Default value: 200 + /// + public static int KeyMaxLength { get; set; } = 200; + + /// + /// Default value: 50 + /// + public static int TypeMaxLength { get; set; } = 50; + + /// + /// Default value: 200 + /// + public static int SubjectIdMaxLength { get; set; } = 200; + + /// + /// Default value: 200 + /// + public static int ClientIdMaxLength { get; set; } = 200; + + /// + /// Default value: 50000 + /// + public static int DataMaxLength { get; set; } = 50000; + + /// + /// Default value: 50000 + /// + public static int DataMaxLengthValue { get; set; } = 50000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceConsts.cs index c60cc34ad8..a7c713e161 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceConsts.cs @@ -2,8 +2,19 @@ { public class IdentityResourceConsts { - public const int NameMaxLength = 200; - public const int DisplayNameMaxLength = 200; - public const int DescriptionMaxLength = 1000; + /// + /// Default value: 200 + /// + public static int NameMaxLength { get; set; } = 200; + + /// + /// Default value: 200 + /// + public static int DisplayNameMaxLength { get; set; } = 200; + + /// + /// Default value: 200 + /// + public static int DescriptionMaxLength { get; set; } = 1000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs index 76635e322e..156088a97b 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs @@ -2,9 +2,21 @@ { public class SecretConsts { - public const int TypeMaxLength = 250; - public const int ValueMaxLength = 4000; + /// + /// Default value: 250 + /// + public static int TypeMaxLength { get; set; } = 250; + + /// + /// Default value: 4000 + /// + public static int ValueMaxLength { get; set; } = 4000; + public static int ValueMaxLengthValue { get; set; } = ValueMaxLength; - public const int DescriptionMaxLength = 2000; + + /// + /// Default value: 2000 + /// + public static int DescriptionMaxLength { get; set; } = 2000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/UserClaimConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/UserClaimConsts.cs index 9133482f1f..cb0279fd2b 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/UserClaimConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/UserClaimConsts.cs @@ -2,6 +2,9 @@ { public class UserClaimConsts { - public const int TypeMaxLength = 200; + /// + /// Default value: 200 + /// + public static int TypeMaxLength { get; set; } = 200; } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionGrantConsts.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionGrantConsts.cs index 8d579638ec..5cb2397349 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionGrantConsts.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionGrantConsts.cs @@ -2,10 +2,19 @@ { public static class PermissionGrantConsts { - public const int MaxNameLength = 128; + /// + /// Default value: 128 + /// + public static int MaxNameLength { get; set; } = 128; - public const int MaxProviderNameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxProviderNameLength { get; set; } = 64; - public const int MaxProviderKeyLength = 64; + /// + /// Default value: 64 + /// + public static int MaxProviderKeyLength { get; set; } = 64; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGrantCacheItem.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGrantCacheItem.cs index e4d3f1648e..49013954a2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGrantCacheItem.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGrantCacheItem.cs @@ -5,8 +5,6 @@ namespace Volo.Abp.PermissionManagement [Serializable] public class PermissionGrantCacheItem { - public string Name { get; set; } //TODO: Consider to remove this - public bool IsGranted { get; set; } public PermissionGrantCacheItem() @@ -14,9 +12,8 @@ namespace Volo.Abp.PermissionManagement } - public PermissionGrantCacheItem(string name, bool isGranted) + public PermissionGrantCacheItem(bool isGranted) { - Name = name; IsGranted = isGranted; } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs index 2ffd6f80e1..bdfdc38881 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Authorization.Permissions; @@ -10,17 +12,21 @@ namespace Volo.Abp.PermissionManagement public class PermissionStore : IPermissionStore, ITransientDependency { public ILogger Logger { get; set; } - + protected IPermissionGrantRepository PermissionGrantRepository { get; } + protected IPermissionDefinitionManager PermissionDefinitionManager { get; } + protected IDistributedCache Cache { get; } - + public PermissionStore( IPermissionGrantRepository permissionGrantRepository, - IDistributedCache cache) + IDistributedCache cache, + IPermissionDefinitionManager permissionDefinitionManager) { PermissionGrantRepository = permissionGrantRepository; Cache = cache; + PermissionDefinitionManager = permissionDefinitionManager; Logger = NullLogger.Instance; } @@ -29,7 +35,10 @@ namespace Volo.Abp.PermissionManagement return (await GetCacheItemAsync(name, providerName, providerKey)).IsGranted; } - protected virtual async Task GetCacheItemAsync(string name, string providerName, string providerKey) + protected virtual async Task GetCacheItemAsync( + string name, + string providerName, + string providerKey) { var cacheKey = CalculateCacheKey(name, providerName, providerKey); @@ -43,23 +52,51 @@ namespace Volo.Abp.PermissionManagement return cacheItem; } - Logger.LogDebug($"Not found in the cache, getting from the repository: {cacheKey}"); + Logger.LogDebug($"Not found in the cache: {cacheKey}"); + + cacheItem = new PermissionGrantCacheItem(false); + + await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); - cacheItem = new PermissionGrantCacheItem( - name, - (await PermissionGrantRepository.FindAsync(name, providerName, providerKey)) != null - ); + return cacheItem; + } - Logger.LogDebug($"Setting the cache item: {cacheKey}"); + protected virtual async Task SetCacheItemsAsync( + string providerName, + string providerKey, + string currentName, + PermissionGrantCacheItem currentCacheItem) + { + var permissions = PermissionDefinitionManager.GetPermissions(); + + Logger.LogDebug($"Getting all granted permissions from the repository for this provider name,key: {providerName},{providerKey}"); - await Cache.SetAsync( - cacheKey, - cacheItem + var grantedPermissionsHashSet = new HashSet( + (await PermissionGrantRepository.GetListAsync(providerName, providerKey)).Select(p => p.Name) ); - Logger.LogDebug($"Finished setting the cache item: {cacheKey}"); + Logger.LogDebug($"Setting the cache items. Count: {permissions.Count}"); - return cacheItem; + var cacheItems = new List>(); + + foreach (var permission in permissions) + { + var isGranted = grantedPermissionsHashSet.Contains(permission.Name); + + cacheItems.Add(new KeyValuePair( + CalculateCacheKey(permission.Name, providerName, providerKey), + new PermissionGrantCacheItem(isGranted)) + ); + + if (permission.Name == currentName) + { + currentCacheItem.IsGranted = isGranted; + } + } + + await Cache.SetManyAsync(cacheItems); + + Logger.LogDebug($"Finished setting the cache items. Count: {permissions.Count}"); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) @@ -67,4 +104,4 @@ namespace Volo.Abp.PermissionManagement return PermissionGrantCacheItem.CalculateCacheKey(name, providerName, providerKey); } } -} +} \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs index 2ed9a4d044..bb9db51f98 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs @@ -5,7 +5,8 @@ using Volo.Abp.AspNetCore.Mvc; namespace Volo.Abp.PermissionManagement { [RemoteService(Name = PermissionManagementRemoteServiceConsts.RemoteServiceName)] - [Area("abp")] + [Area("permissionManagement")] + [Route("api/permission-management/permissions")] public class PermissionsController : AbpController, IPermissionAppService { protected IPermissionAppService PermissionAppService { get; } @@ -15,11 +16,13 @@ namespace Volo.Abp.PermissionManagement PermissionAppService = permissionAppService; } + [HttpGet] public virtual Task GetAsync(string providerName, string providerKey) { return PermissionAppService.GetAsync(providerName, providerKey); } + [HttpPut] public virtual Task UpdateAsync(string providerName, string providerKey, UpdatePermissionsDto input) { return PermissionAppService.UpdateAsync(providerName, providerKey, input); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Properties/launchSettings.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Properties/launchSettings.json deleted file mode 100644 index 6cb3381b6c..0000000000 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55926/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.PermissionManagement.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:55927/" - } - } -} \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/SettingConsts.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/SettingConsts.cs index 44a21f3ccb..e23a0e39a5 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/SettingConsts.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/SettingConsts.cs @@ -2,10 +2,26 @@ { public static class SettingConsts { - public const int MaxNameLength = 128; - public const int MaxValueLength = 2048; + /// + /// Default value: 128 + /// + public static int MaxNameLength { get; set; } = 128; + + /// + /// Default value: 2048 + /// + public static int MaxValueLength { get; set; } = 2048; + public static int MaxValueLengthValue { get; set; } = MaxValueLength; - public const int MaxProviderNameLength = 64; - public const int MaxProviderKeyLength = 64; + + /// + /// Default value: 64 + /// + public static int MaxProviderNameLength { get; set; } = 64; + + /// + /// Default value: 64 + /// + public static int MaxProviderKeyLength { get; set; } = 64; } } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs index d7a8b0ccb1..617d360898 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs @@ -11,23 +11,25 @@ namespace Volo.Abp.SettingManagement public class SettingManagementStore : ISettingManagementStore, ITransientDependency { protected IDistributedCache Cache { get; } + protected ISettingDefinitionManager SettingDefinitionManager { get; } protected ISettingRepository SettingRepository { get; } protected IGuidGenerator GuidGenerator { get; } public SettingManagementStore( ISettingRepository settingRepository, IGuidGenerator guidGenerator, - IDistributedCache cache) + IDistributedCache cache, + ISettingDefinitionManager settingDefinitionManager) { SettingRepository = settingRepository; GuidGenerator = guidGenerator; Cache = cache; + SettingDefinitionManager = settingDefinitionManager; } public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey) { - var cacheItem = await GetCacheItemAsync(name, providerName, providerKey); - return cacheItem.Value; + return (await GetCacheItemAsync(name, providerName, providerKey)).Value; } public virtual async Task SetAsync(string name, string value, string providerName, string providerKey) @@ -70,16 +72,43 @@ namespace Volo.Abp.SettingManagement return cacheItem; } - var setting = await SettingRepository.FindAsync(name, providerName, providerKey); + cacheItem = new SettingCacheItem(null); + + await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); + + return cacheItem; + } - cacheItem = new SettingCacheItem(setting?.Value); + private async Task SetCacheItemsAsync( + string providerName, + string providerKey, + string currentName, + SettingCacheItem currentCacheItem) + { + var settingDefinitions = SettingDefinitionManager.GetAll(); + var settingsDictionary = (await SettingRepository.GetListAsync(providerName, providerKey)) + .ToDictionary(s => s.Name, s => s.Value); + + var cacheItems = new List>(); + + foreach (var settingDefinition in settingDefinitions) + { + var settingValue = settingsDictionary.GetOrDefault(settingDefinition.Name); + + cacheItems.Add( + new KeyValuePair( + CalculateCacheKey(settingDefinition.Name, providerName, providerKey), + new SettingCacheItem(settingValue) + ) + ); - await Cache.SetAsync( - cacheKey, - cacheItem - ); + if (settingDefinition.Name == currentName) + { + currentCacheItem.Value = settingValue; + } + } - return cacheItem; + await Cache.SetManyAsync(cacheItems); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Properties/launchSettings.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Properties/launchSettings.json deleted file mode 100644 index 844334b642..0000000000 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:62272/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.SettingManagement.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:62273/" - } - } -} \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj index b37ef515df..a0197c0686 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj @@ -24,8 +24,6 @@ - - diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs index 19f73b0d51..7dc805ea79 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs @@ -1,12 +1,13 @@ using System.ComponentModel.DataAnnotations; using Volo.Abp.ObjectExtending; +using Volo.Abp.Validation; namespace Volo.Abp.TenantManagement { public abstract class TenantCreateOrUpdateDtoBase : ExtensibleObject { [Required] - [StringLength(TenantConsts.MaxNameLength)] + [DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] public string Name { get; set; } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConnectionStringConsts.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConnectionStringConsts.cs index 4cdf8dee2a..b177e8d046 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConnectionStringConsts.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConnectionStringConsts.cs @@ -2,8 +2,14 @@ { public static class TenantConnectionStringConsts { - public const int MaxNameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxNameLength { get; set; } = 64; - public const int MaxValueLength = 1024; + /// + /// Default value: 1024 + /// + public static int MaxValueLength { get; set; } = 1024; } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConsts.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConsts.cs index 328c422ab9..10de5e8773 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConsts.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConsts.cs @@ -2,6 +2,9 @@ { public static class TenantConsts { - public const int MaxNameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxNameLength { get; set; } = 64; } } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj index 28541197c4..dc168af444 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj @@ -21,7 +21,6 @@ - diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs index 5bf2aeb0b9..1f512c8807 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs @@ -5,7 +5,6 @@ using Volo.Abp.Domain; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; -using Volo.Abp.UI; namespace Volo.Abp.TenantManagement { @@ -14,7 +13,6 @@ namespace Volo.Abp.TenantManagement [DependsOn(typeof(AbpDataModule))] [DependsOn(typeof(AbpDddDomainModule))] [DependsOn(typeof(AbpAutoMapperModule))] - [DependsOn(typeof(AbpUiModule))] //TODO: It's not good to depend on the UI module. However, UserFriendlyException is inside it! public class AbpTenantManagementDomainModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs index d52734a667..a03c96142f 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Volo.Abp.Domain.Services; -using Volo.Abp.UI; namespace Volo.Abp.TenantManagement { diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/ConnectionStringsModal.cshtml.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/ConnectionStringsModal.cshtml.cs index 9093437f98..45a36060e0 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/ConnectionStringsModal.cshtml.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/ConnectionStringsModal.cshtml.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Validation; namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants { @@ -53,7 +54,7 @@ namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants public bool UseSharedDatabase { get; set; } - [StringLength(TenantConnectionStringConsts.MaxValueLength)] + [DynamicStringLength(typeof(TenantConnectionStringConsts), nameof(TenantConnectionStringConsts.MaxValueLength))] public string DefaultConnectionString { get; set; } } } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs index fa517594ee..0c1e6e0bec 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Validation; namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants { @@ -34,7 +35,7 @@ namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants public class TenantInfoModel { [Required] - [StringLength(TenantConsts.MaxNameLength)] + [DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] public string Name { get; set; } [Required] diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs index 74ac176b02..736d0174cf 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Validation; namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants { @@ -42,7 +43,7 @@ namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants public Guid Id { get; set; } [Required] - [StringLength(TenantConsts.MaxNameLength)] + [DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] [Display(Name = "DisplayName:TenantName")] public string Name { get; set; } } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Properties/launchSettings.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Properties/launchSettings.json deleted file mode 100644 index dab1d2b972..0000000000 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55673/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.Abp.TenantManagement.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:55676/" - } - } -} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj index c558ea9637..71ec4cc04a 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj @@ -19,9 +19,7 @@ - - diff --git a/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUserConsts.cs b/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUserConsts.cs index acfce7f37b..efbd2e02b2 100644 --- a/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUserConsts.cs +++ b/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUserConsts.cs @@ -2,14 +2,29 @@ namespace Volo.Abp.Users { public class AbpUserConsts { - public const int MaxUserNameLength = 256; + /// + /// Default value: 256 + /// + public static int MaxUserNameLength { get; set; } = 256; - public const int MaxNameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxNameLength { get; set; } = 64; - public const int MaxSurnameLength = 64; + /// + /// Default value: 64 + /// + public static int MaxSurnameLength { get; set; } = 64; - public const int MaxEmailLength = 256; + /// + /// Default value: 256 + /// + public static int MaxEmailLength { get; set; } = 256; - public const int MaxPhoneNumberLength = 16; + /// + /// Default value: 16 + /// + public static int MaxPhoneNumberLength { get; set; } = 16; } } \ No newline at end of file diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Volo.Abp.VirtualFileExplorer.Web.csproj b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Volo.Abp.VirtualFileExplorer.Web.csproj index 6cbf49a69c..b2f5d2a4a8 100644 --- a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Volo.Abp.VirtualFileExplorer.Web.csproj +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Volo.Abp.VirtualFileExplorer.Web.csproj @@ -30,9 +30,4 @@ - - - - - diff --git a/npm/ng-packs/apps/dev-app/browserslist b/npm/ng-packs/apps/dev-app/.browserslistrc similarity index 100% rename from npm/ng-packs/apps/dev-app/browserslist rename to npm/ng-packs/apps/dev-app/.browserslistrc diff --git a/npm/ng-packs/apps/dev-app/src/app/app-routing.module.ts b/npm/ng-packs/apps/dev-app/src/app/app-routing.module.ts index be5089316a..8f6973079d 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app-routing.module.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app-routing.module.ts @@ -1,36 +1,37 @@ -import { ABP } from '@abp/ng.core'; +import { ABP, DynamicLayoutComponent } from '@abp/ng.core'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; const routes: Routes = [ { path: '', - loadChildren: () => import('./home/home.module').then(m => m.HomeModule), - data: { - routes: { - name: '::Menu:Home', - order: 1, - } as ABP.Route, - }, - }, - { - path: 'account', - loadChildren: () => - import('@abp/ng.account').then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), - }, - { - path: 'identity', - loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()), - }, - { - path: 'tenant-management', - loadChildren: () => - import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()), - }, - { - path: 'setting-management', - loadChildren: () => - import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()), + component: DynamicLayoutComponent, + children: [ + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./home/home.module').then(m => m.HomeModule), + }, + { + path: 'account', + loadChildren: () => + import('@abp/ng.account').then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), + }, + { + path: 'identity', + loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()), + }, + { + path: 'tenant-management', + loadChildren: () => + import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()), + }, + { + path: 'setting-management', + loadChildren: () => + import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()), + }, + ], }, ]; diff --git a/npm/ng-packs/apps/dev-app/src/app/app.module.ts b/npm/ng-packs/apps/dev-app/src/app/app.module.ts index 71fedf421b..4e69d73cd0 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app.module.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app.module.ts @@ -13,6 +13,7 @@ import { NgxsModule } from '@ngxs/store'; import { environment } from '../environments/environment'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; +import { APP_ROUTE_PROVIDER } from './route.provider'; const LOGGERS = [NgxsLoggerPluginModule.forRoot({ disabled: false })]; @@ -35,6 +36,7 @@ const LOGGERS = [NgxsLoggerPluginModule.forRoot({ disabled: false })]; ThemeBasicModule.forRoot(), ...(environment.production ? [] : LOGGERS), ], + providers: [APP_ROUTE_PROVIDER], declarations: [AppComponent], bootstrap: [AppComponent], }) diff --git a/npm/ng-packs/apps/dev-app/src/app/home/home-routing.module.ts b/npm/ng-packs/apps/dev-app/src/app/home/home-routing.module.ts index 367affb583..7089990134 100644 --- a/npm/ng-packs/apps/dev-app/src/app/home/home-routing.module.ts +++ b/npm/ng-packs/apps/dev-app/src/app/home/home-routing.module.ts @@ -1,15 +1,8 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './home.component'; -import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; -const routes: Routes = [ - { - path: '', - component: ApplicationLayoutComponent, - children: [{ path: '', component: HomeComponent }], - }, -]; +const routes: Routes = [{ path: '', component: HomeComponent }]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/npm/ng-packs/apps/dev-app/src/app/route.provider.ts b/npm/ng-packs/apps/dev-app/src/app/route.provider.ts new file mode 100644 index 0000000000..5a14ad7ba0 --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/app/route.provider.ts @@ -0,0 +1,20 @@ +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { APP_INITIALIZER } from '@angular/core'; + +export const APP_ROUTE_PROVIDER = [ + { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, +]; + +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + path: '/', + name: '::Menu:Home', + iconClass: 'fas fa-home', + order: 1, + layout: eLayoutType.application, + }, + ]); + }; +} diff --git a/npm/ng-packs/apps/dev-app/tsconfig.dev.json b/npm/ng-packs/apps/dev-app/tsconfig.dev.json index cfbd94952d..ac0a5c3d24 100644 --- a/npm/ng-packs/apps/dev-app/tsconfig.dev.json +++ b/npm/ng-packs/apps/dev-app/tsconfig.dev.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "../../out-tsc/app", "types": [] diff --git a/npm/ng-packs/apps/dev-app/tsconfig.spec.json b/npm/ng-packs/apps/dev-app/tsconfig.spec.json index e487d98612..f21e414d94 100644 --- a/npm/ng-packs/apps/dev-app/tsconfig.spec.json +++ b/npm/ng-packs/apps/dev-app/tsconfig.spec.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "../../out-tsc/spec", "types": ["node", "jest"] diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index 37476c4fd8..6ea5faeeae 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -19,7 +19,7 @@ "ci:test": "ng test --coverage=false", "ci:build": "cd scripts && yarn build:prod", "lerna": "lerna", - "compile:ivy": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points --tsconfig './tsconfig.prod.json'", + "compile:ivy": "yarn ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points --tsconfig './tsconfig.prod.json' --source node_modules --async false", "postinstall": "npm run compile:ivy" }, "devDependencies": { @@ -32,35 +32,35 @@ "@abp/ng.tenant-management": "~2.9.0", "@abp/ng.theme.basic": "~2.9.0", "@abp/ng.theme.shared": "~2.9.0", - "@abp/utils": "^2.9.0", + "@abp/utils": "^3.0.0-alpha.1", "@angular-builders/jest": "^9.0.1", - "@angular-devkit/build-angular": "~0.901.7", - "@angular-devkit/build-ng-packagr": "~0.901.7", - "@angular/animations": "~9.1.9", - "@angular/cli": "~9.1.7", - "@angular/common": "~9.1.9", - "@angular/compiler": "~9.1.9", - "@angular/compiler-cli": "~9.1.9", - "@angular/core": "~9.1.9", - "@angular/forms": "~9.1.9", - "@angular/language-service": "~9.1.9", - "@angular/localize": "~9.1.9", - "@angular/platform-browser": "~9.1.9", - "@angular/platform-browser-dynamic": "~9.1.9", - "@angular/router": "~9.1.9", + "@angular-devkit/build-angular": "~0.1000.0", + "@angular-devkit/build-ng-packagr": "~0.1000.0", + "@angular/animations": "~10.0.0", + "@angular/cli": "~10.0.0", + "@angular/common": "~10.0.0", + "@angular/compiler": "~10.0.0", + "@angular/compiler-cli": "~10.0.0", + "@angular/core": "~10.0.0", + "@angular/forms": "~10.0.0", + "@angular/language-service": "~10.0.0", + "@angular/localize": "~10.0.0", + "@angular/platform-browser": "~10.0.0", + "@angular/platform-browser-dynamic": "~10.0.0", + "@angular/router": "~10.0.0", "@fortawesome/fontawesome-free": "^5.13.0", "@ng-bootstrap/ng-bootstrap": "^6.1.0", - "@ngneat/spectator": "^4.5.0", - "@ngx-validate/core": "^0.0.8", + "@ngneat/spectator": "^5.11.0", + "@ngx-validate/core": "^0.0.9", "@ngxs/devtools-plugin": "^3.6.2", "@ngxs/logger-plugin": "^3.6.2", "@ngxs/router-plugin": "^3.6.2", "@ngxs/storage-plugin": "^3.6.2", "@ngxs/store": "^3.6.2", - "@swimlane/ngx-datatable": "^16.0.0", + "@swimlane/ngx-datatable": "^17.0.0", "@types/jest": "^25.2.3", "@types/node": "^12.11.1", - "angular-oauth2-oidc": "^8.0.4", + "angular-oauth2-oidc": "^9.2.2", "bootstrap": "^4.5.0", "chart.js": "^2.9.3", "codelyzer": "^5.1.2", @@ -73,23 +73,23 @@ "just-clone": "^3.1.0", "just-compare": "^1.3.0", "lerna": "^3.19.0", - "ng-packagr": "^9.1.5", + "ng-packagr": "^10.0.0", "ngxs-reset-plugin": "^1.3.0", "ngxs-schematic": "^1.1.9", "prettier": "^1.18.2", - "protractor": "~5.4.0", + "protractor": "~7.0.0", "rxjs": "~6.5.4", "snq": "^1.0.3", "symlink-manager": "^1.5.0", "ts-node": "~7.0.0", - "ts-toolbelt": "^6.3.6", - "tsickle": "^0.37.0", - "tslint": "~5.20.0", - "typescript": "~3.8.0", + "ts-toolbelt": "^6.9.9", + "tsickle": "^0.38.1", + "tslint": "~6.1.0", + "typescript": "~3.9.5", "zone.js": "~0.10.2" }, "dependencies": { - "tslib": "^1.10.0" + "tslib": "^2.0.0" }, "config": { "commitizen": { @@ -99,4 +99,4 @@ "resolutions": { "@ngx-validate/core": "^0.0.8" } -} +} \ No newline at end of file diff --git a/npm/ng-packs/packages/account/ngcc.config.js b/npm/ng-packs/packages/account/ngcc.config.js new file mode 100644 index 0000000000..93f34d5655 --- /dev/null +++ b/npm/ng-packs/packages/account/ngcc.config.js @@ -0,0 +1,7 @@ +module.exports = { + entryPoints: { + '.': {}, + './config': {}, + './dist': { ignore: true }, + }, +}; diff --git a/npm/ng-packs/packages/account/package.json b/npm/ng-packs/packages/account/package.json index 254b6031a1..9f4c7942d2 100644 --- a/npm/ng-packs/packages/account/package.json +++ b/npm/ng-packs/packages/account/package.json @@ -8,7 +8,8 @@ }, "dependencies": { "@abp/ng.theme.shared": "~2.9.0" - }, + , "tslib": "^2.0.0" +}, "publishConfig": { "access": "public" } diff --git a/npm/ng-packs/packages/core/package.json b/npm/ng-packs/packages/core/package.json index fd326aae58..5566fb9750 100644 --- a/npm/ng-packs/packages/core/package.json +++ b/npm/ng-packs/packages/core/package.json @@ -8,15 +8,16 @@ }, "dependencies": { "@abp/utils": "^2.9.0", - "@angular/localize": "~9.1.9", + "@angular/localize": "~9.1.11", "@ngxs/router-plugin": "^3.6.2", "@ngxs/storage-plugin": "^3.6.2", "@ngxs/store": "^3.6.2", - "angular-oauth2-oidc": "^8.0.4", + "angular-oauth2-oidc": "^9.2.2", "just-clone": "^3.1.0", "just-compare": "^1.3.0", "snq": "^1.0.3", - "ts-toolbelt": "^6.3.6" + "ts-toolbelt": "^6.9.9", + "tslib": "^2.0.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 25d49fb169..49a0868967 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -1,4 +1,4 @@ -import { Component, Injector, OnDestroy, Type } from '@angular/core'; +import { Component, Injector, OnDestroy, Type, Optional, SkipSelf } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { Store } from '@ngxs/store'; import { eLayoutType } from '../enums/common'; @@ -23,7 +23,12 @@ import { TreeNode } from '../utils/tree-utils'; export class DynamicLayoutComponent implements OnDestroy { layout: Type; - constructor(injector: Injector, private store: Store) { + constructor( + injector: Injector, + private store: Store, + @Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent, + ) { + if (dynamicLayoutComponent) return; const route = injector.get(ActivatedRoute); const router = injector.get(Router); const routes = injector.get(RoutesService); diff --git a/npm/ng-packs/packages/core/src/lib/directives/visibility.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/visibility.directive.ts index 2dffa1532d..f5f4d22f5e 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/visibility.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/visibility.directive.ts @@ -2,6 +2,10 @@ import { Directive, Input, Optional, ElementRef, Renderer2, AfterViewInit } from import { Subject } from 'rxjs'; import snq from 'snq'; +/** + * + * @deprecated To be deleted in v3.3 + */ @Directive({ selector: '[abpVisibility]', }) @@ -40,7 +44,8 @@ export class VisibilityDirective implements AfterViewInit { setTimeout(() => { const htmlNodes = snq( - () => Array.from(this.focusedElement.childNodes).filter(node => node instanceof HTMLElement), + () => + Array.from(this.focusedElement.childNodes).filter(node => node instanceof HTMLElement), [], ); diff --git a/npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts b/npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts index 637f7b4165..9ddc03e493 100644 --- a/npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts +++ b/npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts @@ -20,7 +20,7 @@ export class RoutesHandler { return; } - const routes = flatRoutes([{ ...data.routes, path }], { path: '' }); + const routes = flatRoutes([{ path, ...data.routes }], { path: '' }); this.routes.add(routes); }); } @@ -30,13 +30,13 @@ function flatRoutes(routes: RouteDef[], parent: any) { if (!routes) return []; return routes.reduce((acc, route) => { - const current = { + const { children, ...current } = { ...route, parentName: parent.name, - path: parent.path + '/' + route.path, + path: (parent.path + '/' + route.path).replace(/\/\//g, '/'), }; - acc.push(current, ...flatRoutes(current.children, current)); + acc.push(current, ...flatRoutes(children, current)); return acc; }, []); diff --git a/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts b/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts index b52afe9faf..1f3bb65f4e 100644 --- a/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts +++ b/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts @@ -62,10 +62,11 @@ export namespace ApplicationConfiguration { } export interface CurrentUser { - isAuthenticated: boolean; + email: string; id: string; + isAuthenticated: boolean; + roles: string[]; tenantId: string; userName: string; - email: string; } } diff --git a/npm/ng-packs/packages/core/src/lib/services/auth.service.ts b/npm/ng-packs/packages/core/src/lib/services/auth.service.ts index 0c38b22acf..35f667bf20 100644 --- a/npm/ng-packs/packages/core/src/lib/services/auth.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/auth.service.ts @@ -20,14 +20,14 @@ export class AuthService { private oAuthService: OAuthService, private store: Store, @Optional() @Inject('ACCOUNT_OPTIONS') private options: any, - ) {} - - login(username: string, password: string): Observable { - const tenant = this.store.selectSnapshot(SessionState.getTenant); - + ) { this.oAuthService.configure( this.store.selectSnapshot(ConfigState.getOne('environment')).oAuthConfig, ); + } + + login(username: string, password: string): Observable { + const tenant = this.store.selectSnapshot(SessionState.getTenant); return from(this.oAuthService.loadDiscoveryDocument()).pipe( switchMap(() => @@ -63,7 +63,7 @@ export class AuthService { ) .pipe( switchMap(() => { - this.oAuthService.logOut(); + this.oAuthService.logOut(true); return this.store.dispatch(new GetAppConfiguration()); }), ); diff --git a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts index 7257a50ff3..2f80d6b44d 100644 --- a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts @@ -126,12 +126,18 @@ export abstract class AbstractTreeService { } } +@Injectable() export abstract class AbstractNavTreeService extends AbstractTreeService implements OnDestroy { readonly id = 'name'; readonly parentId = 'parentName'; readonly hide = (item: T) => item.invisible || !this.isGranted(item); - readonly sort = (a: T, b: T) => a.order - b.order; + readonly sort = (a: T, b: T) => { + if (!a.order) return 1; + if (!b.order) return -1; + + return a.order - b.order; + }; constructor(protected actions: Actions, protected store: Store) { super(); @@ -145,6 +151,11 @@ export abstract class AbstractNavTreeService extends Abstract return this.store.selectSnapshot(ConfigState.getGrantedPolicy(requiredPolicy)); } + hasChildren(identifier: string): boolean { + const node = this.find(item => item[this.id] === identifier); + return Boolean(node?.children?.length); + } + hasInvisibleChild(identifier: string): boolean { const node = this.find(item => item[this.id] === identifier); return node?.children?.some(child => child.invisible); diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index 4bbf25ade2..d9793529aa 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -190,7 +190,7 @@ export class ConfigState { @Action(GetAppConfiguration) addData({ patchState, dispatch }: StateContext) { - const apiName = this.store.selectSnapshot(ConfigState.getDeep('environment.application.name')); + const apiName = 'default'; const api = this.store.selectSnapshot(ConfigState.getApiUrl(apiName)); return this.http .get(`${api}/api/abp/application-configuration`) diff --git a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts index a34e3eaa4e..4b0c9aa682 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts @@ -1,9 +1,9 @@ import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; -import { ConfigStateService } from '../services/config-state.service'; -import { ConfigState } from '../states'; import { Store } from '@ngxs/store'; -import { Config } from '../models/config'; import * as ConfigActions from '../actions'; +import { Config } from '../models/config'; +import { ConfigStateService } from '../services/config-state.service'; +import { ConfigState } from '../states'; const CONFIG_STATE_DATA = { environment: { @@ -86,6 +86,7 @@ const CONFIG_STATE_DATA = { tenantId: null, userName: null, email: null, + roles: [], }, features: { values: {}, diff --git a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts index 5966ba0728..0f9aa08c1a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts @@ -90,6 +90,7 @@ export const CONFIG_STATE_DATA = { tenantId: null, userName: null, email: null, + roles: [], }, features: { values: {}, diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts index 93214442ea..aa5e51cb17 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts @@ -33,10 +33,10 @@ describe('Routes Service', () => { const visible = await service.visible$.pipe(take(1)).toPromise(); expect(flat.length).toBe(4); - expect(flat[0].name).toBe('foo'); - expect(flat[1].name).toBe('baz'); - expect(flat[2].name).toBe('x'); - expect(flat[3].name).toBe('bar'); + expect(flat[3].name).toBe('foo'); + expect(flat[0].name).toBe('baz'); + expect(flat[1].name).toBe('x'); + expect(flat[2].name).toBe('bar'); expect(tree.length).toBe(1); expect(tree[0].name).toBe('foo'); @@ -68,6 +68,16 @@ describe('Routes Service', () => { }); }); + describe('#hasChildren', () => { + it('should return if node has invisible child', () => { + service.add(routes); + + expect(service.hasChildren('foo')).toBe(true); + expect(service.hasChildren('bar')).toBe(true); + expect(service.hasChildren('baz')).toBe(false); + }); + }); + describe('#hasInvisibleChild', () => { it('should return if node has invisible child', () => { service.add(routes); @@ -88,8 +98,8 @@ describe('Routes Service', () => { const visible = service.visible; expect(flat.length).toBe(2); - expect(flat[0].name).toBe('foo'); - expect(flat[1].name).toBe('x'); + expect(flat[1].name).toBe('foo'); + expect(flat[0].name).toBe('x'); expect(tree.length).toBe(1); expect(tree[0].name).toBe('foo'); @@ -114,10 +124,10 @@ describe('Routes Service', () => { const visible = service.visible; expect(flat.length).toBe(4); - expect(flat[0].name).toBe('foo'); - expect(flat[1].name).toBe('baz'); - expect(flat[2].name).toBe('x'); - expect(flat[3].name).toBe('bar'); + expect(flat[3].name).toBe('foo'); + expect(flat[0].name).toBe('baz'); + expect(flat[1].name).toBe('x'); + expect(flat[2].name).toBe('bar'); expect(tree.length).toBe(1); expect(tree[0].name).toBe('foo'); diff --git a/npm/ng-packs/packages/feature-management/package.json b/npm/ng-packs/packages/feature-management/package.json index fd1816df45..e2550bf15a 100644 --- a/npm/ng-packs/packages/feature-management/package.json +++ b/npm/ng-packs/packages/feature-management/package.json @@ -7,7 +7,8 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~2.9.0" + "@abp/ng.theme.shared": "~2.9.0", + "tslib": "^2.0.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts b/npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts index 1020e60d4e..4aabf7bf80 100644 --- a/npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts +++ b/npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts @@ -15,7 +15,7 @@ export class FeatureManagementService { getFeatures(params: FeatureManagement.Provider): Observable { const request: Rest.Request = { method: 'GET', - url: '/api/abp/features', + url: '/api/feature-management/features', params, }; return this.rest.request(request, { @@ -30,7 +30,7 @@ export class FeatureManagementService { }: FeatureManagement.Provider & FeatureManagement.Features): Observable { const request: Rest.Request = { method: 'PUT', - url: '/api/abp/features', + url: '/api/feature-management/features', body: { features }, params: { providerKey, providerName }, }; diff --git a/npm/ng-packs/packages/identity/ngcc.config.js b/npm/ng-packs/packages/identity/ngcc.config.js new file mode 100644 index 0000000000..93f34d5655 --- /dev/null +++ b/npm/ng-packs/packages/identity/ngcc.config.js @@ -0,0 +1,7 @@ +module.exports = { + entryPoints: { + '.': {}, + './config': {}, + './dist': { ignore: true }, + }, +}; diff --git a/npm/ng-packs/packages/identity/package.json b/npm/ng-packs/packages/identity/package.json index 38ec9cd9d9..553b5518e9 100644 --- a/npm/ng-packs/packages/identity/package.json +++ b/npm/ng-packs/packages/identity/package.json @@ -8,7 +8,8 @@ }, "dependencies": { "@abp/ng.permission-management": "~2.9.0", - "@abp/ng.theme.shared": "~2.9.0" + "@abp/ng.theme.shared": "~2.9.0", + "tslib": "^2.0.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html index 648b1024d0..bf49550f12 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html @@ -102,109 +102,110 @@ >
- - - -
-
- * - -
+ + +
diff --git a/npm/ng-packs/packages/identity/src/lib/identity.module.ts b/npm/ng-packs/packages/identity/src/lib/identity.module.ts index 9aeadac6a2..036c7e3607 100644 --- a/npm/ng-packs/packages/identity/src/lib/identity.module.ts +++ b/npm/ng-packs/packages/identity/src/lib/identity.module.ts @@ -2,7 +2,7 @@ import { CoreModule, LazyModuleFactory } from '@abp/ng.core'; import { PermissionManagementModule } from '@abp/ng.permission-management'; import { ThemeSharedModule } from '@abp/ng.theme.shared'; import { ModuleWithProviders, NgModule, NgModuleFactory } from '@angular/core'; -import { NgbDropdownModule, NgbTabsetModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbDropdownModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; import { NgxValidateCoreModule } from '@ngx-validate/core'; import { NgxsModule } from '@ngxs/store'; import { RolesComponent } from './components/roles/roles.component'; @@ -17,7 +17,7 @@ import { IdentityState } from './states/identity.state'; NgxsModule.forFeature([IdentityState]), CoreModule, IdentityRoutingModule, - NgbTabsetModule, + NgbNavModule, ThemeSharedModule, NgbDropdownModule, PermissionManagementModule, diff --git a/npm/ng-packs/packages/permission-management/package.json b/npm/ng-packs/packages/permission-management/package.json index a09d4c7f8e..1bc7859bcf 100644 --- a/npm/ng-packs/packages/permission-management/package.json +++ b/npm/ng-packs/packages/permission-management/package.json @@ -7,7 +7,8 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~2.9.0" + "@abp/ng.theme.shared": "~2.9.0", + "tslib": "^2.0.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/permission-management/src/lib/services/permission-management.service.ts b/npm/ng-packs/packages/permission-management/src/lib/services/permission-management.service.ts index fa6433e952..4825036f81 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/services/permission-management.service.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/services/permission-management.service.ts @@ -16,7 +16,7 @@ export class PermissionManagementService { ): Observable { const request: Rest.Request = { method: 'GET', - url: '/api/abp/permissions', + url: '/api/permission-management/permissions', params, }; @@ -33,7 +33,7 @@ export class PermissionManagementService { }: PermissionManagement.GrantedProvider & PermissionManagement.UpdateRequest): Observable { const request: Rest.Request = { method: 'PUT', - url: '/api/abp/permissions', + url: '/api/permission-management/permissions', body: { permissions }, params: { providerKey, providerName }, }; diff --git a/npm/ng-packs/packages/setting-management/config/src/enums/index.ts b/npm/ng-packs/packages/setting-management/config/src/enums/index.ts index 4a7a6a0e23..3bda94b078 100644 --- a/npm/ng-packs/packages/setting-management/config/src/enums/index.ts +++ b/npm/ng-packs/packages/setting-management/config/src/enums/index.ts @@ -1,2 +1 @@ -export * from './policy-names'; export * from './route-names'; diff --git a/npm/ng-packs/packages/setting-management/config/src/enums/policy-names.ts b/npm/ng-packs/packages/setting-management/config/src/enums/policy-names.ts deleted file mode 100644 index df66e95e3b..0000000000 --- a/npm/ng-packs/packages/setting-management/config/src/enums/policy-names.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const enum eSettingManagementPolicyNames { - Settings = 'AbpAccount.SettingManagement', -} diff --git a/npm/ng-packs/packages/setting-management/config/src/providers/route.provider.ts b/npm/ng-packs/packages/setting-management/config/src/providers/route.provider.ts index badc46ae9a..28c0fb7388 100644 --- a/npm/ng-packs/packages/setting-management/config/src/providers/route.provider.ts +++ b/npm/ng-packs/packages/setting-management/config/src/providers/route.provider.ts @@ -2,7 +2,6 @@ import { eLayoutType, RoutesService, SettingTabsService } from '@abp/ng.core'; import { eThemeSharedRouteNames } from '@abp/ng.theme.shared'; import { APP_INITIALIZER } from '@angular/core'; import { debounceTime, map } from 'rxjs/operators'; -import { eSettingManagementPolicyNames } from '../enums/policy-names'; import { eSettingManagementRouteNames } from '../enums/route-names'; export const SETTING_MANAGEMENT_ROUTE_PROVIDERS = [ @@ -22,9 +21,8 @@ export function configureRoutes(routes: RoutesService) { name: eSettingManagementRouteNames.Settings, path: '/setting-management', parentName: eThemeSharedRouteNames.Administration, - requiredPolicy: eSettingManagementPolicyNames.Settings, layout: eLayoutType.application, - order: 6, + order: 100, iconClass: 'fa fa-cog', }, ]); diff --git a/npm/ng-packs/packages/setting-management/ngcc.config.js b/npm/ng-packs/packages/setting-management/ngcc.config.js new file mode 100644 index 0000000000..93f34d5655 --- /dev/null +++ b/npm/ng-packs/packages/setting-management/ngcc.config.js @@ -0,0 +1,7 @@ +module.exports = { + entryPoints: { + '.': {}, + './config': {}, + './dist': { ignore: true }, + }, +}; diff --git a/npm/ng-packs/packages/setting-management/package.json b/npm/ng-packs/packages/setting-management/package.json index 969510305e..a6fb57ce18 100644 --- a/npm/ng-packs/packages/setting-management/package.json +++ b/npm/ng-packs/packages/setting-management/package.json @@ -7,7 +7,8 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~2.9.0" + "@abp/ng.theme.shared": "~2.9.0", + "tslib": "^2.0.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/tenant-management/ngcc.config.js b/npm/ng-packs/packages/tenant-management/ngcc.config.js new file mode 100644 index 0000000000..93f34d5655 --- /dev/null +++ b/npm/ng-packs/packages/tenant-management/ngcc.config.js @@ -0,0 +1,7 @@ +module.exports = { + entryPoints: { + '.': {}, + './config': {}, + './dist': { ignore: true }, + }, +}; diff --git a/npm/ng-packs/packages/tenant-management/package.json b/npm/ng-packs/packages/tenant-management/package.json index 0921e0dd9e..e8fd10094e 100644 --- a/npm/ng-packs/packages/tenant-management/package.json +++ b/npm/ng-packs/packages/tenant-management/package.json @@ -8,7 +8,8 @@ }, "dependencies": { "@abp/ng.feature-management": "~2.9.0", - "@abp/ng.theme.shared": "~2.9.0" + "@abp/ng.theme.shared": "~2.9.0", + "tslib": "^2.0.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/theme-basic/package.json b/npm/ng-packs/packages/theme-basic/package.json index c7e3275e91..9307b1e21e 100644 --- a/npm/ng-packs/packages/theme-basic/package.json +++ b/npm/ng-packs/packages/theme-basic/package.json @@ -8,7 +8,8 @@ }, "dependencies": { "@abp/ng.theme.shared": "~2.9.0" - }, + , "tslib": "^2.0.0" +}, "publishConfig": { "access": "public" } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/actions/index.ts b/npm/ng-packs/packages/theme-basic/src/lib/actions/index.ts deleted file mode 100644 index 2e29080de8..0000000000 --- a/npm/ng-packs/packages/theme-basic/src/lib/actions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './layout.actions'; diff --git a/npm/ng-packs/packages/theme-basic/src/lib/actions/layout.actions.ts b/npm/ng-packs/packages/theme-basic/src/lib/actions/layout.actions.ts deleted file mode 100644 index 3786ea82b7..0000000000 --- a/npm/ng-packs/packages/theme-basic/src/lib/actions/layout.actions.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Layout } from '../models/layout'; - -export class AddNavigationElement { - static readonly type = '[Layout] Add Navigation Element'; - constructor(public payload: Layout.NavigationElement | Layout.NavigationElement[]) {} -} - -export class RemoveNavigationElementByName { - static readonly type = '[Layout] Remove Navigation ElementByName'; - constructor(public name: string) {} -} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html index c4acbec880..eb68dc7df0 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html @@ -34,21 +34,15 @@
-
+ +
diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/index.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/index.ts index 06b6d5339c..6c4de6cf21 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/index.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/index.ts @@ -2,6 +2,8 @@ export * from './account-layout/account-layout.component'; export * from './application-layout/application-layout.component'; export * from './empty-layout/empty-layout.component'; export * from './logo/logo.component'; +export * from './nav-items/current-user.component'; +export * from './nav-items/languages.component'; export * from './nav-items/nav-items.component'; export * from './routes/routes.component'; export * from './validation-error/validation-error.component'; diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts new file mode 100644 index 0000000000..70238d3538 --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts @@ -0,0 +1,67 @@ +import { ApplicationConfiguration, AuthService, ConfigState } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Select } from '@ngxs/store'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'abp-current-user', + // tslint:disable-next-line: component-max-inline-declarations + template: ` + + {{ + 'AbpAccount::Login' | abpLocalization + }} + + + `, +}) +export class CurrentUserComponent implements OnInit { + @Select(ConfigState.getOne('currentUser')) + currentUser$: Observable; + + get smallScreen(): boolean { + return window.innerWidth < 992; + } + + constructor(private authService: AuthService, private router: Router) {} + + ngOnInit() {} + + logout() { + this.authService.logout().subscribe(() => { + this.router.navigate(['/'], { state: { redirectUrl: this.router.url } }); + }); + } +} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts new file mode 100644 index 0000000000..115e9e563e --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts @@ -0,0 +1,88 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Store, Select } from '@ngxs/store'; +import { SetLanguage, ConfigState, ApplicationConfiguration, SessionState } from '@abp/ng.core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import snq from 'snq'; + +@Component({ + selector: 'abp-languages', + // tslint:disable-next-line: component-max-inline-declarations + template: ` + + `, +}) +export class LanguagesComponent implements OnInit { + get smallScreen(): boolean { + return window.innerWidth < 992; + } + + @Select(ConfigState.getDeep('localization.languages')) + languages$: Observable; + + get defaultLanguage$(): Observable { + return this.languages$.pipe( + map( + languages => + snq( + () => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName, + ), + '', + ), + ); + } + + get dropdownLanguages$(): Observable { + return this.languages$.pipe( + map( + languages => + snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)), + [], + ), + ); + } + + get selectedLangCulture(): string { + return this.store.selectSnapshot(SessionState.getLanguage); + } + + constructor(private store: Store) {} + + ngOnInit() {} + + onChangeLang(cultureName: string) { + this.store.dispatch(new SetLanguage(cultureName)); + } +} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.html index cb86773db5..955ccb8878 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.html @@ -1,81 +1,16 @@ - - - - + - + diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts index 6819807cd7..7a5ee9b37a 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts @@ -1,125 +1,12 @@ -import { - ABP, - ApplicationConfiguration, - AuthService, - ConfigState, - SessionState, - SetLanguage, - takeUntilDestroy, -} from '@abp/ng.core'; -import { - AfterViewInit, - Component, - Input, - OnDestroy, - TemplateRef, - TrackByFunction, - ViewChild, -} from '@angular/core'; -import { Navigate, RouterState } from '@ngxs/router-plugin'; -import { Select, Store } from '@ngxs/store'; -import compare from 'just-compare'; -import { Observable } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; -import snq from 'snq'; -import { AddNavigationElement } from '../../actions/layout.actions'; -import { eNavigationElementNames } from '../../enums/navigation-element-names'; -import { Layout } from '../../models/layout'; -import { LayoutState } from '../../states/layout.state'; +import { NavItem, NavItemsService } from '@abp/ng.theme.shared'; +import { Component, Input, TrackByFunction } from '@angular/core'; @Component({ selector: 'abp-nav-items', templateUrl: 'nav-items.component.html', }) -export class NavItemsComponent implements AfterViewInit, OnDestroy { - @Select(LayoutState.getNavigationElements) - navElements$: Observable; +export class NavItemsComponent { + trackByFn: TrackByFunction = (_, element) => element.id; - @Select(ConfigState.getOne('currentUser')) - currentUser$: Observable; - - @Select(ConfigState.getDeep('localization.languages')) - languages$: Observable; - - @ViewChild('currentUser', { static: false, read: TemplateRef }) - currentUserRef: TemplateRef; - - @ViewChild('language', { static: false, read: TemplateRef }) - languageRef: TemplateRef; - - @Input() - smallScreen: boolean; - - rightPartElements: TemplateRef[] = []; - - trackByFn: TrackByFunction = (_, element) => element.name; - - get defaultLanguage$(): Observable { - return this.languages$.pipe( - map( - languages => - snq( - () => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName, - ), - '', - ), - ); - } - - get dropdownLanguages$(): Observable { - return this.languages$.pipe( - map( - languages => - snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)), - [], - ), - ); - } - - get selectedLangCulture(): string { - return this.store.selectSnapshot(SessionState.getLanguage); - } - - constructor(private store: Store, private authService: AuthService) {} - - ngAfterViewInit() { - const navigations = this.store - .selectSnapshot(LayoutState.getNavigationElements) - .map(({ name }) => name); - - if (navigations.indexOf(eNavigationElementNames.Language) < 0) { - this.store.dispatch( - new AddNavigationElement([ - { element: this.languageRef, order: 4, name: eNavigationElementNames.Language }, - { element: this.currentUserRef, order: 5, name: eNavigationElementNames.User }, - ]), - ); - } - - this.navElements$ - .pipe( - map(elements => elements.map(({ element }) => element)), - filter(elements => !compare(elements, this.rightPartElements)), - takeUntilDestroy(this), - ) - .subscribe(elements => { - setTimeout(() => (this.rightPartElements = elements), 0); - }); - } - - ngOnDestroy() {} - - onChangeLang(cultureName: string) { - this.store.dispatch(new SetLanguage(cultureName)); - } - - logout() { - this.authService.logout().subscribe(() => { - this.store.dispatch( - new Navigate(['/'], null, { - state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url }, - }), - ); - }); - } + constructor(public readonly navItems: NavItemsService) {} } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html index d987f22860..42bdd7376a 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html @@ -18,10 +18,10 @@