diff --git a/common.props b/common.props index 19b6f81fe6..ab7b86e3b2 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 2.7.0 + 2.8.0 $(NoWarn);CS1591 https://abp.io/assets/abp_nupkg.png https://abp.io diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/Post.md b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/Post.md new file mode 100644 index 0000000000..b6c1d3f952 --- /dev/null +++ b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/Post.md @@ -0,0 +1,247 @@ +# ABP Framework v2.7.0 Has Been Released! + +The **ABP Framework** & and the **ABP Commercial** v2.7 have been released. We hadn't created blog post for the 2.4, 2.4 and 2.6 releases, so this post will also cover **what's new** with these releases and **what we've done** in the last 2 months. + +## About the Release Cycle & Development + +Reminding that we had started to release a new minor feature version **in every two weeks**, generally on Thursdays. Our goal is to deliver new features as soon as possible. + +We've completed & merged hundreds of issues and pull requests with **1,300+ commits** in the last 7-8 weeks, only for the ABP Framework repository. Daily commit counts are constantly increasing: + +![github-contribution-graph](github-contribution-graph.png) + +ABP.IO Platform is rapidly growing and we are getting more and more contributions from the community. + +## What's New in the ABP Framework? + +### Object Extending System + +In the last few releases, we've mostly focused on providing ways to extend existing modules when you use them as NuGet/NPM Packages. + +The Object Extending System allows module developers to create extensible modules and allows application developers to customize and extend a module easily. + +For example, you can add two extension properties to the user entity of the identity module: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdate(options => + { + options.AddOrUpdateProperty("SocialSecurityNumber"); + options.AddOrUpdateProperty("IsSuperUser"); + } + ); +```` + +It is easy to define validation rules for the properties: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "SocialSecurityNumber", + options => + { + options.Attributes.Add(new RequiredAttribute()); + options.Attributes.Add( + new StringLengthAttribute(32) { + MinimumLength = 6 + } + ); + }); +```` + +You can even write custom code to validate the property. It automatically works for the objects those are parameters of an application service, controller or a page. + +While extension properties of an entity are normally stored in a single JSON formatted field in the database table, you can easily configure to store a property as a table field using the EF Core mapping: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "SocialSecurityNumber", + options => + { + options.MapEfCore(b => b.HasMaxLength(32)); + } + ); +```` + +See the [Object Extensions document](https://docs.abp.io/en/abp/latest/Object-Extensions) for details about this system. + +See also the [Customizing the Existing Modules](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Guide) guide to learn all the possible customization options. + +### Text Templating Package + +[Volo.Abp.TextTemplating](https://www.nuget.org/packages/Volo.Abp.TextTemplating) is a new package introduced with the v2.7.0. Previously, [Volo.Abp.Emailing](https://www.nuget.org/packages/Volo.Abp.Emailing) package had a similar functionality but it was limited, experimental and tightly coupled to the emailing. + +The new text templating package allows you to define text based templates those can be easily localized and reused. You can define layout templates and share the layout from other templates. + +We are currently using it for email sending. A module needs to send an email typically defines a template. Example: + +````xml +

{{L "PasswordReset"}}

+ +

{{L "PasswordResetInfoInEmail"}}

+ + +```` + +This is a typical password reset email template. + +* The template system is based on the open source [Scriban library](https://github.com/lunet-io/scriban). So it supports if conditions, loops and much more. +* `model` is used to pass data to the template (just like the ASP.NET Core MVC). +* `L` is a special function that localizes the given string. + +It is typical to use the same layout for all emails. So, you can define a layout template. This is the standard layout template comes with the framework: + +````xml + + + + + + + {{content}} + + +```` + +A layout should have a `{{content}}` area to render the child content (just like the `RenderBody()` in the MVC). + +It is very easy to override a template content by the final application to customize it. + +Whenever you need to render a template, use the `ITemplateRenderer` service by providing the template name and a model. See the [text templating documentation](https://docs.abp.io/en/abp/latest/Text-Templating) for details. We've even created a UI for the ABP Commercial (see the related section below). + +### Subscribing to the Exceptions + +ABP Framework's [exception handling system](https://docs.abp.io/en/abp/latest/Exception-Handling) automatically handles exceptions and returns an appropriate result to the client. In some cases, you may want to have a callback that is notified whenever an exception occurs. In this way, for example, you can send an email or take any action based on the exception. + +Just create a class derived from the `ExceptionSubscriber` class in your application: + +````csharp +public class MyExceptionSubscriber : ExceptionSubscriber +{ + public override async Task HandleAsync(ExceptionNotificationContext context) + { + //TODO... + } +} +```` + +See the [exception handling](https://docs.abp.io/en/abp/latest/Exception-Handling) document for more. + +### Others + +There are many minor features and enhancements made to the framework in the past releases. Here, a few ones: + +* Added `AbpLocalizationOptions.DefaultResourceType` to set the default resource type for the application. In this way, the localization system uses the default resource whenever the resource was not specified. The latest application startup template already configures it, but you may want to set it for your existing applications. +* Added `IsEnabled` to permission definition. In this way, you can completely disable a permission and hide the related functionality from the application. This can be a way of feature switch for some applications. See [#3486](https://github.com/abpframework/abp/issues/3486) for usage. +* Added Dutch and German localizations to all the localization resources defined by the framework. Thanks to the contributors. + +## What's New in the ABP Commercial + +The goal of the [ABP Commercial](https://commercial.abp.io/) is to provide pre-build application functionalities, code generation tools, professional themes, advanced samples and premium support for ABP Framework based projects. + +We are working on the ABP Commercial in the parallel to align with the ABP Framework features and provide more modules, theme options and tooling. + +This section explains what's going on the ABP Commercial side. + +### Module Entity Extension System + +Module entity extension system is a higher level API that uses the object extension system (introduced above) and provides an easy way to add extension properties to existing entities. A new extension property easily automatically becomes a part of the HTTP API and the User Interface. + +Example: Add a `SocialSecurityNumber` to the user entity of the identity module + +````csharp +ObjectExtensionManager.Instance.Modules() + .ConfigureIdentity(identity => + { + identity.ConfigureUser(user => + { + user.AddOrUpdateProperty( //property type: string + "SocialSecurityNumber", //property name + property => + { + //validation rules + property.Attributes.Add(new RequiredAttribute()); + property.Attributes.Add( + new StringLengthAttribute(64) { + MinimumLength = 4 + } + ); + + //...other configurations for this property + } + ); + }); + }); +```` + +With just such a configuration, the user interface will have the new property (on the table and on the create/edit forms): + +![module-entity-extended-ui](module-entity-extended-ui.png) + +The new property can be easily localized and validated. Currently, it supports primitive types like string, number and boolean, but we planned to add more advanced scenarios by the time (like navigation/lookup properties). + +See the [Module Entity Extensions](https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions) guide to learn how to use it and configure details. + +#### Other Extension Points + +There are also some other pre-defined points to customize and extend the user interface of a depended module: + +* You can add a new action for an entity on the data table (left side on the picture below). +* You can add new buttons (or other controls) to the page toolbar (right side on the picture below). +* You can add custom columns to a data table. + +![abp-commercial-ui-extensions](abp-commercial-ui-extensions.png) + +See the [Customizing the Modules](https://docs.abp.io/en/commercial/latest/guides/customizing-modules) guide to learn all the possible ways to customize a depended module. + +### Text Template Management Module + +We are introducing a new module with the v2.7 release: [Text Template Management](https://docs.abp.io/en/commercial/latest/modules/text-template-management). It is basically used to edit text/email templates (introduced with the ABP Framework 2.7) on the user interface and save changed in the database. + +A screenshot from the content editing for the password reset email template: + +![text-template-content-ui](text-template-content-ui.png) + +This module comes pre-installed when you create a new project. + +### Entity History Views + +Audit logging UI module now shows all the entity changes in the application with property change details. + +![audit-log-entity-changes](audit-log-entity-changes.png) + +You can also check history for an entity when you click to the actions menu for the entity: + +![tenant-entity-changes](tenant-entity-changes.png) + +### More Samples + +We are creating more advanced sample applications built with the ABP Commercial. Easy CRM is one of them which will be available in a few days to the commercial customers. + +Here, a screenshot from the Easy CRM dashboard: + +![easy-crm](easy-crm.png) + +It has accounts, contacts, product groups, products, orders and so on. + +### New Modules + +We continue to improve existing modules and creating new modules. In addition to the new [text template management](https://docs.abp.io/en/commercial/latest/modules/text-template-management) module introduced above; + +* We've recently released a [payment module](https://commercial.abp.io/modules/Volo.Payment) that currently works with PayU and 2Checkout payment gateways. More gateways will be added by the time. +* We've created a simple [Twilio SMS integration](https://docs.abp.io/en/commercial/latest/modules/twilio-sms) module to send SMS over the Twilio. +* We are working on a **chat module** that is currently being developed and will be available in the next weeks. +* We are working on the **organization unit management** system for the identity module to create hierarchical organization units (domain layer will be open source & free). + +More modules, theme and tooling options are being developed for the ABP Commercial and the ABP Framework. + +## ABP Framework vs ABP Commercial + +We ([Volosoft](https://volosoft.com/) - the core team behind the ABP.IO platform), are spending almost equal time on the ABP Framework and the ABP Commercial and we consider the ABP.IO platform as a whole. + +[ABP Framework](https://abp.io/) provides all the infrastructure and application independent framework features to make you more productive, focus on your own business code and implement software development best practices. It provides you a well defined and comfortable development experience without repeating yourself. + +[ABP Commercial](https://commercial.abp.io/) provides pre-built functionalities, themes and tooling to save your time if your requirements involve these functionalities in addition to the premium support for the framework and the pre-built modules. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/abp-commercial-ui-extensions.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/abp-commercial-ui-extensions.png new file mode 100644 index 0000000000..52fe37a712 Binary files /dev/null and b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/abp-commercial-ui-extensions.png differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/audit-log-entity-changes.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/audit-log-entity-changes.png new file mode 100644 index 0000000000..e06b3300ee Binary files /dev/null and b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/audit-log-entity-changes.png differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/easy-crm.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/easy-crm.png new file mode 100644 index 0000000000..e40f399525 Binary files /dev/null and b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/easy-crm.png differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/github-contribution-graph.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/github-contribution-graph.png new file mode 100644 index 0000000000..ff3db2ad22 Binary files /dev/null and b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/github-contribution-graph.png differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/module-entity-extended-ui.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/module-entity-extended-ui.png new file mode 100644 index 0000000000..25621aabb7 Binary files /dev/null and b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/module-entity-extended-ui.png differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/tenant-entity-changes.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/tenant-entity-changes.png new file mode 100644 index 0000000000..4cd573c588 Binary files /dev/null and b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/tenant-entity-changes.png differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/text-template-content-ui.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/text-template-content-ui.png new file mode 100644 index 0000000000..037e3e4d8b Binary files /dev/null and b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/text-template-content-ui.png differ diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md index e0772579ec..00e791a94e 100644 --- a/docs/en/Entity-Framework-Core-Migrations.md +++ b/docs/en/Entity-Framework-Core-Migrations.md @@ -546,6 +546,8 @@ Entity extension system solves the main problem of the extra properties: It can All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database. +See the [entity extension system](Customizing-Application-Modules-Extending-Entities.md) for details. + ###### Creating a New Table Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table. diff --git a/docs/en/Getting-Started.md b/docs/en/Getting-Started.md index 72e2f2886c..fa7f47f276 100644 --- a/docs/en/Getting-Started.md +++ b/docs/en/Getting-Started.md @@ -25,6 +25,12 @@ The following tools should be installed on your development machine: * [Node v12+](https://nodejs.org) * [Yarn v1.19+](https://classic.yarnpkg.com/) +{{ if Tiered == "Yes" }} + +* [Redis](https://redis.io/): The applications use Redis as as [distributed cache](../Caching.md). So, you need to have Redis installed & running. + +{{ end }} + > You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core. diff --git a/docs/en/Text-Templating.md b/docs/en/Text-Templating.md new file mode 100644 index 0000000000..efde511323 --- /dev/null +++ b/docs/en/Text-Templating.md @@ -0,0 +1,3 @@ +# Text-Templating + +TODO \ No newline at end of file diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index c684d1fc1c..be812cc162 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -756,28 +756,28 @@ Open a new command line interface (terminal window) and go to your `angular` fol yarn ``` -#### BooksModule +#### BookModule -Run the following command line to create a new module, named `BooksModule`: +Run the following command line to create a new module, named `BookModule`: ```bash -yarn ng generate module books --route books --module app.module +yarn ng generate module book --routing true ``` -![Generating books module](./images/bookstore-creating-books-module-terminal.png) +![Generating books module](./images/bookstore-creating-book-module-terminal.png) #### Routing -Open the `app-routing.module.ts` file in `src\app` folder. Add the new `import` and replace `books` path as shown below +Open the `app-routing.module.ts` file in `src\app` folder. Add the new `import` and add a route as shown below ```js import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added this line to imports <== -//...replaced original books path with the below +//...added books path with the below to the routes array { path: 'books', component: ApplicationLayoutComponent, - loadChildren: () => import('./books/books.module').then(m => m.BooksModule), + loadChildren: () => import('./book/book.module').then(m => m.BookModule), data: { routes: { name: '::Menu:Books', @@ -789,71 +789,50 @@ import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added th * 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. -Run `yarn start` and wait for Angular to serve the application: - -```bash -yarn start -``` - -Open the browser and navigate to http://localhost:4200/books. You'll see a blank page saying "*books works!*". - -![initial-books-page](./images/bookstore-initial-books-page-with-layout.png) - #### Book list component -Replace the `books.component.html` in the `app\books` folder with the following content: - -```html - -``` - -Then run the command below on the terminal in the root folder to generate a new component, named book-list: +Run the command below on the terminal in the root folder to generate a new component, named book-list: ```bash -yarn ng generate component books/book-list +yarn ng generate component book/book-list ``` ![Creating books list](./images/bookstore-creating-book-list-terminal.png) -Open `books.module.ts` file in the `app\books` folder and replace the content as below: +Open `book.module.ts` file in the `app\book` folder and replace the content as below: ```js import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; - -import { BooksRoutingModule } from './books-routing.module'; -import { BooksComponent } from './books.component'; +import { BookRoutingModule } from './book-routing.module'; import { BookListComponent } from './book-list/book-list.component'; import { SharedModule } from '../shared/shared.module'; //<== added this line ==> @NgModule({ - declarations: [BooksComponent, BookListComponent], + declarations: [BookListComponent], imports: [ CommonModule, - BooksRoutingModule, + BookRoutingModule, SharedModule, //<== added this line ==> - ] + ], }) -export class BooksModule { } +export class BookModule {} ``` * We imported `SharedModule` and added to `imports` array. -Open `books-routing.module.ts` file in the `app\books` folder and replace the content as below: +Open `book-routing.module.ts` file in the `app\book` folder and replace the content as below: ```js import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; +import { BookListComponent } from './book-list/book-list.component'; // <== added this line ==> -import { BooksComponent } from './books.component'; -import { BookListComponent } from './book-list/book-list.component'; //<== added this line ==> - -//<== replaced routes ==> +// <== replaced routes ==> const routes: Routes = [ { path: '', - component: BooksComponent, - children: [{ path: '', component: BookListComponent }], + component: BookListComponent, }, ]; @@ -861,36 +840,42 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) -export class BooksRoutingModule { } +export class BookRoutingModule { } ``` * We imported `BookListComponent` and replaced `routes` const. -We'll see **book-list works!** text on the books page: +Run `yarn start` and wait for Angular to serve the application: + +```bash +yarn start +``` + +Open the browser and navigate to http://localhost:4200/books. We'll see **book-list works!** text on the books page: ![Initial book list page](./images/bookstore-initial-book-list-page.png) -#### Create BooksState +#### Create BookState Run the following command in the terminal to create a new state, named `BooksState`: ```bash -npx @ngxs/cli --name books --directory src/app/books +npx @ngxs/cli --name book --directory src/app/book ``` -* This command creates `books.state.ts` and `books.actions.ts` files in the `src/app/books/state` folder. See the [NGXS CLI documentation](https://www.ngxs.io/plugins/cli). +* 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 `BooksState` to the `app.module.ts` in the `src/app` folder and then add the `BooksState` to `forRoot` static method of `NgxsModule` as an array element of the first parameter of the method. +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 { BooksState } from './books/state/books.state'; //<== imported BooksState ==> +import { BookState } from './books/state/book.state'; //<== imported BookState ==> @NgModule({ imports: [ // other imports - NgxsModule.forRoot([BooksState]), //<== added BooksState ==> + NgxsModule.forRoot([BookState]), //<== added BookState ==> //other imports ], @@ -919,46 +904,46 @@ The generated files looks like below: 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 `books.actions.ts` file in `app/books/state` folder and replace the content below: +Open the `book.actions.ts` file in `app/book/state` folder and replace the content below: ```js export class GetBooks { - static readonly type = '[Books] Get'; + static readonly type = '[Book] Get'; } ``` -#### Implement BooksState +#### Implement BookState -Open the `books.state.ts` file in `app/books/state` folder and replace the content below: +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 './books.actions'; -import { BookService } from '../../app/shared/services'; +import { GetBooks } from './book.actions'; +import { BookService } from '../services'; import { tap } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { BookDto } from '../../app/shared/models'; +import { BookDto } from '../models'; -export class BooksStateModel { +export class BookStateModel { public book: PagedResultDto; } -@State({ - name: 'BooksState', - defaults: { book: {} } as BooksStateModel, +@State({ + name: 'BookState', + defaults: { book: {} } as BookStateModel, }) @Injectable() -export class BooksState { +export class BookState { @Selector() - static getBooks(state: BooksStateModel) { + static getBooks(state: BookStateModel) { return state.book.items || []; } constructor(private bookService: BookService) {} @Action(GetBooks) - get(ctx: StateContext) { + get(ctx: StateContext) { return this.bookService.getListByInput().pipe( tap((booksResponse) => { ctx.patchState({ @@ -969,22 +954,23 @@ export class BooksState { } } ``` -* We added the book property to BooksStateModel model. -* We added the `GetBooks` action that retrieves the books data via `BooksService` that generated via ABP CLI and patches the state. + +* 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\books\book-list` folder and replace the content as below: +Open the `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 '../../app/shared/models'; -import { GetBooks } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks } from '../state/book.actions'; +import { BookState } from '../state/book.state'; @Component({ selector: 'app-book-list', @@ -992,7 +978,7 @@ import { BooksState } from '../state/books.state'; styleUrls: ['./book-list.component.scss'], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -1018,7 +1004,7 @@ export class BookListComponent implements OnInit { * 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. -Open the `book-list.component.html` file in `app\books\book-list` folder and replace the content as below: +Open the `book-list.component.html` file in `app\book\book-list` folder and replace the content as below: ```html
diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index cfe9936812..69f3e5436b 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -458,54 +458,54 @@ In this section, you will learn how to create a new modal dialog form to create #### State definitions -Open `books.action.ts` in `books\state` folder and replace the content as below: +Open `book.action.ts` in `app\book\state` folder and replace the content as below: ```js -import { CreateUpdateBookDto } from '../../app/shared/models'; //<== added this line ==> +import { CreateUpdateBookDto } from '../models'; //<== added this line ==> export class GetBooks { - static readonly type = '[Books] Get'; + static readonly type = '[Book] Get'; } // added CreateUpdateBook class export class CreateUpdateBook { - static readonly type = '[Books] Create Update Book'; + static readonly type = '[Book] Create Update Book'; constructor(public payload: CreateUpdateBookDto) { } } ``` * We imported the `CreateUpdateBookDto` model and created the `CreateUpdateBook` action. -Open `books.state.ts` file in `books\state` folder and replace the content as below: +Open `book.state.ts` file in `app\book\state` folder and replace the content as below: ```js import { PagedResultDto } from '@abp/ng.core'; import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks, CreateUpdateBook } from './books.actions'; // <== added CreateUpdateBook==> -import { BookService } from '../../app/shared/services'; +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 '../../app/shared/models'; +import { BookDto } from '../models'; -export class BooksStateModel { +export class BookStateModel { public book: PagedResultDto; } -@State({ - name: 'BooksState', - defaults: { book: {} } as BooksStateModel, +@State({ + name: 'BookState', + defaults: { book: {} } as BookStateModel, }) @Injectable() -export class BooksState { +export class BookState { @Selector() - static getBooks(state: BooksStateModel) { + static getBooks(state: BookStateModel) { return state.book.items || []; } constructor(private bookService: BookService) {} @Action(GetBooks) - get(ctx: StateContext) { + get(ctx: StateContext) { return this.bookService.getListByInput().pipe( tap((bookResponse) => { ctx.patchState({ @@ -517,7 +517,7 @@ export class BooksState { // added CreateUpdateBook action listener @Action(CreateUpdateBook) - save(ctx: StateContext, action: CreateUpdateBook) { + save(ctx: StateContext, action: CreateUpdateBook) { return this.bookService.createByInput(action.payload); } } @@ -605,16 +605,16 @@ 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 `books\book-list` 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 { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks } from '../state/book.actions'; +import { BookState } from '../state/book.state'; @Component({ selector: 'app-book-list', @@ -622,7 +622,7 @@ import { BooksState } from '../state/books.state'; styleUrls: ['./book-list.component.scss'], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -662,16 +662,16 @@ You can open your browser and click **New book** button to see the new modal. [Reactive forms](https://angular.io/guide/reactive-forms) provide a model-driven approach to handling form inputs whose values change over time. -Open `book-list.component.ts` file in `app\books\book-list` 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 { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks } from '../state/book.actions'; +import { BookState } from '../state/book.state'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==> @Component({ @@ -680,7 +680,7 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== adde styleUrls: ['./book-list.component.scss'], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -774,42 +774,40 @@ Open `book-list.component.html` in `app\books\book-list` folder and replace ` @NgModule({ - declarations: [BooksComponent, BookListComponent], + declarations: [BookListComponent], imports: [ CommonModule, - BooksRoutingModule, + BookRoutingModule, SharedModule, - NgbDatepickerModule //<== added this line ==> - ] + NgbDatepickerModule, //<== added this line ==> + ], }) -export class BooksModule { } +export class BookModule {} ``` * We imported `NgbDatepickerModule` to be able to use the date picker. -Open `book-list.component.ts` file in `app\books\book-list` 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 { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks } from '../state/book.actions'; +import { BookState } from '../state/book.state'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==> @@ -820,7 +818,7 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added this line ==> }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -885,16 +883,16 @@ Now, you can open your browser to see the changes: #### Saving the book -Open `book-list.component.ts` file in `app\books\book-list` 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 { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks, CreateUpdateBook } from '../state/books.actions'; // <== added CreateUpdateBook ==> -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks, CreateUpdateBook } from '../state/book.actions'; // <== added CreateUpdateBook ==> +import { BookState } from '../state/book.state'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; @@ -905,12 +903,11 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; - //added bookTypeArr array bookTypeArr = Object.keys(BookType).filter( (bookType) => typeof this.booksType[bookType] === 'number' ); @@ -949,7 +946,7 @@ export class BookListComponent implements OnInit { }); } - //<== added save ==> + // <== added save ==> save() { if (this.form.invalid) { return; @@ -967,7 +964,7 @@ export class BookListComponent implements OnInit { * We imported `CreateUpdateBook`. * We added `save` method -Open `book-list.component.html` in `app\books\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 and add the following `abp-button` to save the new book. ```html @@ -1001,28 +998,28 @@ The final modal UI looks like below: #### CreateUpdateBook action -Open the `books.actions.ts` in `books\state` folder and replace the content as below: +Open the `book.actions.ts` in `app\book\state` folder and replace the content as below: ```js -import { CreateUpdateBookDto } from '../../app/shared/models'; +import { CreateUpdateBookDto } from '../models'; export class GetBooks { - static readonly type = '[Books] Get'; + static readonly type = '[Book] Get'; } export class CreateUpdateBook { - static readonly type = '[Books] Create Update Book'; - constructor(public payload: CreateUpdateBookDto, public id?: string) { } // <== added id parameter ==> + 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 `books.state.ts` in `books\state` folder and replace the `save` method as below: +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) { +save(ctx: StateContext, action: CreateUpdateBook) { if (action.id) { return this.bookService.updateByIdAndInput(action.payload, action.id); } else { @@ -1033,19 +1030,19 @@ save(ctx: StateContext, action: CreateUpdateBook) { #### BookListComponent -Open `book-list.component.ts` in `app\books\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 inject `BookService` dependency by adding it to the constructor and add a variable named `selectedBook`. ```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 '../../app/shared/models'; -import { GetBooks, CreateUpdateBook } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks, CreateUpdateBook } from '../state/book.actions'; +import { BookState } from '../state/book.state'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; -import { BookService } from '../../app/shared/services'; // <== imported BookService ==> +import { BookService } from '../services'; // <== imported BookService ==> @Component({ selector: 'app-book-list', @@ -1054,7 +1051,7 @@ import { BookService } from '../../app/shared/services'; // <== imported BookSer providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -1141,7 +1138,7 @@ export class BookListComponent implements OnInit { #### Add "Actions" dropdown to the table -Open the `book-list.component.html` in `app\books\book-list` folder and replace the `
` tag as below: +Open the `book-list.component.html` in `app\book\book-list` folder and replace the `
` tag as below: ```html
@@ -1199,7 +1196,7 @@ The final UI looks like as below: ![Action buttons](./images/bookstore-actions-buttons.png) -Open `book-list.component.html` in `app\books\book-list` folder and find the `` tag and replace the content as below. +Open `book-list.component.html` in `app\book\book-list` folder and find the `` tag and replace the content as below. ```html @@ -1213,45 +1210,45 @@ Open `book-list.component.html` in `app\books\book-list` folder and find the ` -import { BookService } from '../../app/shared/services'; +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 '../../app/shared/models'; +import { BookDto } from '../models'; -export class BooksStateModel { +export class BookStateModel { public book: PagedResultDto; } -@State({ - name: 'BooksState', - defaults: { book: {} } as BooksStateModel, +@State({ + name: 'BookState', + defaults: { book: {} } as BookStateModel, }) @Injectable() -export class BooksState { +export class BookState { @Selector() - static getBooks(state: BooksStateModel) { + static getBooks(state: BookStateModel) { return state.book.items || []; } constructor(private bookService: BookService) {} @Action(GetBooks) - get(ctx: StateContext) { + get(ctx: StateContext) { return this.bookService.getListByInput().pipe( tap((booksResponse) => { ctx.patchState({ @@ -1262,7 +1259,7 @@ export class BooksState { } @Action(CreateUpdateBook) - save(ctx: StateContext, action: CreateUpdateBook) { + save(ctx: StateContext, action: CreateUpdateBook) { if (action.id) { return this.bookService.updateByIdAndInput(action.payload, action.id); } else { @@ -1272,7 +1269,7 @@ export class BooksState { // <== added DeleteBook action listener ==> @Action(DeleteBook) - delete(ctx: StateContext, action: DeleteBook) { + delete(ctx: StateContext, action: DeleteBook) { return this.bookService.deleteById(action.id); } } @@ -1285,7 +1282,7 @@ export class BooksState { #### Delete confirmation popup -Open `book-list.component.ts` in`app\books\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: @@ -1309,7 +1306,7 @@ See the [Confirmation Popup documentation](https://docs.abp.io/en/abp/latest/UI/ In the `book-list.component.ts` add a delete method : ```js -import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/books.actions' ;// <== imported DeleteBook ==> +import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/book.actions' ;// <== imported DeleteBook ==> import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation ==> @@ -1335,7 +1332,7 @@ The `delete` method shows a confirmation popup and subscribes for the user respo #### Add a delete button -Open `book-list.component.html` in `app\books\book-list` folder and modify the `ngbDropdownMenu` to add the delete button as shown below: +Open `book-list.component.html` in `app\book\book-list` folder and modify the `ngbDropdownMenu` to add the delete button as shown below: ```html
diff --git a/docs/en/Tutorials/images/bookstore-angular-file-tree.png b/docs/en/Tutorials/images/bookstore-angular-file-tree.png index a3197b6457..ffa8dcd7e2 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 9e6cc9e010..d402895c9b 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-creating-book-list-terminal.png b/docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png index 6f19dcc7bf..13829db60f 100644 Binary files a/docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png and b/docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png differ diff --git a/docs/en/Tutorials/images/bookstore-creating-book-module-terminal.png b/docs/en/Tutorials/images/bookstore-creating-book-module-terminal.png new file mode 100644 index 0000000000..c935a6f130 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-creating-book-module-terminal.png differ diff --git a/docs/en/Tutorials/images/bookstore-creating-books-module-terminal.png b/docs/en/Tutorials/images/bookstore-creating-books-module-terminal.png deleted file mode 100644 index ec9ef4c42f..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-creating-books-module-terminal.png and /dev/null differ diff --git a/docs/en/Tutorials/images/generated-proxies.png b/docs/en/Tutorials/images/generated-proxies.png index 9e466e7d55..1d322c0765 100644 Binary files a/docs/en/Tutorials/images/generated-proxies.png and b/docs/en/Tutorials/images/generated-proxies.png differ diff --git a/docs/zh-Hans/Getting-Started.md b/docs/zh-Hans/Getting-Started.md index a2b3013fe5..b60fccfaa2 100644 --- a/docs/zh-Hans/Getting-Started.md +++ b/docs/zh-Hans/Getting-Started.md @@ -24,6 +24,11 @@ * [Node v12+](https://nodejs.org) * [Yarn v1.19+](https://classic.yarnpkg.com/) +{{ if Tiered == "Yes" }} + +* [Redis](https://redis.io/): 应用程序将Redis用作[分布式缓存](../Caching.md). 因此你需要安装并运行Redis. + +{{ end }} > 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器. diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/AbpTenantController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/AbpTenantController.cs index b84ffc421f..768a3331f3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/AbpTenantController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/AbpTenantController.cs @@ -1,11 +1,14 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.MultiTenancy; namespace Pages.Abp.MultiTenancy { + [Area("abp")] + [RemoteService(Name = "abp")] [Route("api/abp/multi-tenancy")] public class AbpTenantController : AbpController, IAbpTenantAppService { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/de.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/de.json new file mode 100644 index 0000000000..98909d9847 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/de.json @@ -0,0 +1,12 @@ +{ + "culture": "de", + "texts": { + "GivenTenantIsNotAvailable": "Der angegebene Mandant ist nicht verfügbar: {0}", + "Tenant": "Mandant", + "Switch": "wechseln", + "Name": "Name", + "SwitchTenantHint": "Lassen Sie das Namensfeld leer, um auf die Host-Seite zu wechseln.", + "SwitchTenant": "Mandant wechseln", + "NotSelected": "Nicht ausgewählt" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json new file mode 100644 index 0000000000..2e3971055e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json @@ -0,0 +1,12 @@ +{ + "culture": "nl", + "texts": { + "GivenTenantIsNotAvailable": "Gegeven klant is niet beschikbaar: {0}", + "Tenant": "Klant", + "Switch": "Schakel over", + "Name": "Name", + "SwitchTenantHint": "Laat het naamveld leeg om over te schakelen naar de hostkant.", + "SwitchTenant": "Klant wisselen", + "NotSelected": "Niet geselecteerd" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/DefaultBrandingProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/DefaultBrandingProvider.cs index eb13a1cea0..a85c03364c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/DefaultBrandingProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/DefaultBrandingProvider.cs @@ -7,5 +7,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components public virtual string AppName => "MyApplication"; public virtual string LogoUrl => null; + + public virtual string LogoReverseUrl => null; } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/IBrandingProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/IBrandingProvider.cs index 65e3cb8883..1c20f7fc6e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/IBrandingProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/IBrandingProvider.cs @@ -4,6 +4,14 @@ { string AppName { get; } + /// + /// Logo on white background + /// string LogoUrl { get; } + + /// + /// Logo on dark background + /// + string LogoReverseUrl { get; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js index 19882fbc62..9ee545615c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js @@ -144,7 +144,8 @@ var tableProperty = properties[i]; columnConfigs.push({ title: localizeDisplayName(tableProperty.name, tableProperty.config.displayName), - data: "extraProperties." + tableProperty.name + data: "extraProperties." + tableProperty.name, + orderable: false }); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs index e1c2d1f859..09807391f6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs @@ -3,6 +3,8 @@ using Volo.Abp.Http.Modeling; namespace Volo.Abp.AspNetCore.Mvc.ApiExploring { + [Area("abp")] + [RemoteService(Name = "abp")] [Route("api/abp/api-definition")] public class AbpApiDefinitionController : AbpController, IRemoteService { 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 187823b663..98fd94882d 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 @@ -63,7 +63,9 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations { //TODO: Optimize & cache..? - return new ApplicationConfigurationDto + Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetAsync()..."); + + var result = new ApplicationConfigurationDto { Auth = await GetAuthConfigAsync(), Features = await GetFeaturesConfigAsync(), @@ -74,6 +76,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations CurrentTenant = GetCurrentTenant(), ObjectExtensions = _cachedObjectExtensionsDtoService.Get() }; + + Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetAsync()."); + + return result; } protected virtual CurrentTenantDto GetCurrentTenant() @@ -107,35 +113,25 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations protected virtual async Task GetAuthConfigAsync() { - Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetAuthConfigAsync()"); - var authConfig = new ApplicationAuthConfigurationDto(); var policyNames = await _abpAuthorizationPolicyProvider.GetPoliciesNamesAsync(); - Logger.LogDebug($"GetPoliciesNamesAsync returns {policyNames.Count} items."); - foreach (var policyName in policyNames) { authConfig.Policies[policyName] = true; - Logger.LogDebug($"_authorizationService.IsGrantedAsync? {policyName}"); - if (await _authorizationService.IsGrantedAsync(policyName)) { authConfig.GrantedPolicies[policyName] = true; } } - Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetAuthConfigAsync()"); - return authConfig; } protected virtual async Task GetLocalizationConfigAsync() { - Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetLocalizationConfigAsync()"); - var localizationConfig = new ApplicationLocalizationConfigurationDto(); localizationConfig.Languages.AddRange(await _languageProvider.GetLanguagesAsync()); @@ -165,8 +161,6 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations ); } - Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetLocalizationConfigAsync()"); - return localizationConfig; } @@ -197,8 +191,6 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations private async Task GetSettingConfigAsync() { - Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetSettingConfigAsync()"); - var result = new ApplicationSettingConfigurationDto { Values = new Dictionary() @@ -214,15 +206,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations result.Values[settingDefinition.Name] = await _settingProvider.GetOrNullAsync(settingDefinition.Name); } - Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetSettingConfigAsync()"); - return result; } protected virtual async Task GetFeaturesConfigAsync() { - Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetFeaturesConfigAsync()"); - var result = new ApplicationFeatureConfigurationDto(); foreach (var featureDefinition in _featureDefinitionManager.GetAll()) @@ -235,8 +223,6 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations result.Values[featureDefinition.Name] = await FeatureChecker.GetOrNullAsync(featureDefinition.Name); } - Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetFeaturesConfigAsync()"); - return result; } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs index 67672986be..67c373585e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs @@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc; namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations { + [Area("abp")] + [RemoteService(Name = "abp")] [Route("api/abp/application-configuration")] public class AbpApplicationConfigurationController : AbpController, IAbpApplicationConfigurationAppService { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs index 6cc01966a5..d4ce99dc12 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs @@ -12,6 +12,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations [Area("Abp")] [Route("Abp/ApplicationConfigurationScript")] [DisableAuditing] + [RemoteService(false)] + [ApiExplorerSettings(IgnoreApi = true)] public class AbpApplicationConfigurationScriptController : AbpController { private readonly IAbpApplicationConfigurationAppService _configurationAppService; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs index 9b376ab540..3f25eb2f84 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Localization { [Area("Abp")] [Route("Abp/Languages/[action]")] + [RemoteService(false)] + [ApiExplorerSettings(IgnoreApi = true)] public class AbpLanguagesController : AbpController { [HttpGet] diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs index 99cada9602..45df8d47ad 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs @@ -10,6 +10,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting [Area("Abp")] [Route("Abp/ServiceProxyScript")] [DisableAuditing] + [RemoteService(false)] + [ApiExplorerSettings(IgnoreApi = true)] public class AbpServiceProxyScriptController : AbpController { private readonly IProxyScriptManager _proxyScriptManager; diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs index 1b71dad8cb..4fab6d0001 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Volo.Abp.DependencyInjection; @@ -91,9 +93,27 @@ namespace Volo.Abp.AspNetCore.VirtualFileSystem protected virtual IFileProvider CreateFileProvider() { - return new CompositeFileProvider( + var fileProviders = new List() + { new PhysicalFileProvider(_hostingEnvironment.ContentRootPath), _virtualFileProvider + }; + + if (_hostingEnvironment.IsDevelopment() && + _hostingEnvironment.WebRootFileProvider is CompositeFileProvider compositeFileProvider) + { + var staticWebAssetsFileProvider = compositeFileProvider + .FileProviders + .FirstOrDefault(f => f.GetType().Name.Equals("StaticWebAssetsFileProvider")); + + if (staticWebAssetsFileProvider != null) + { + fileProviders.Add(staticWebAssetsFileProvider); + } + } + + return new CompositeFileProvider( + fileProviders ); } 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 accf5eddb9..5c732f6873 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 @@ -20,6 +20,7 @@ namespace Volo.Abp.Cli.Commands { public static Dictionary> propertyList = new Dictionary>(); public ILogger Logger { get; set; } + public static string outputPrefix = "src/app"; protected TemplateProjectBuilder TemplateProjectBuilder { get; } @@ -100,14 +101,22 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation($"{rootPath} directory is creating"); - Directory.CreateDirectory($"src/app/{rootPath}/shared/models"); - Directory.CreateDirectory($"src/app/{rootPath}/shared/services"); + if (rootPath == "app") + { + outputPrefix = "src"; + } + else + { + outputPrefix = "src/app"; + } - var serviceIndexList = new List(); - var modelIndexList = new List(); + Directory.CreateDirectory($"{outputPrefix}/{rootPath}"); foreach (var controller in moduleValue.Root.ToList().Select(item => item.First)) { + var serviceIndexList = new List(); + var modelIndexList = new List(); + var serviceFileText = new StringBuilder(); serviceFileText.AppendLine("[firstTypeList]"); @@ -128,6 +137,12 @@ namespace Volo.Abp.Cli.Commands var controllerName = (string)controller["controllerName"]; var controllerServiceName = controllerName.PascalToKebabCase() + ".service.ts"; + var controllerPathName = controllerName.ToLower().Replace("controller", ""); + controllerPathName = (controllerPathName.StartsWith(rootPath)) ? controllerPathName.Substring(rootPath.Length) : controllerPathName; + + Directory.CreateDirectory($"{outputPrefix}/{rootPath}/{controllerPathName}/models"); + Directory.CreateDirectory($"{outputPrefix}/{rootPath}/{controllerPathName}/services"); + foreach (var actionItem in controller["actions"]) { var action = actionItem.First; @@ -157,7 +172,7 @@ namespace Volo.Abp.Cli.Commands var isOptional = (bool)parameter["isOptional"]; var defaultValue = (string)parameter["defaultValue"]; - var modelIndex = CreateType(data, (string)parameter["type"], rootPath, modelIndexList); + var modelIndex = CreateType(data, (string)parameter["type"], rootPath, modelIndexList, controllerPathName); if (!string.IsNullOrWhiteSpace(modelIndex)) { @@ -194,7 +209,7 @@ namespace Volo.Abp.Cli.Commands parameterModel = AddParameter(name, typeSimple, isOptional, defaultValue, bindingSourceId, parameterModel); } - modelIndex = CreateType(data, (string)parameterOnMethod["type"], rootPath, modelIndexList); + modelIndex = CreateType(data, (string)parameterOnMethod["type"], rootPath, modelIndexList, controllerPathName); if (!string.IsNullOrWhiteSpace(modelIndex)) { @@ -225,7 +240,7 @@ namespace Volo.Abp.Cli.Commands foreach (var parameterItem in parameterModel.OrderBy(p => p.DisplayOrder)) { var parameterItemModelName = parameterItem.Type.PascalToKebabCase() + ".ts"; - var parameterItemModelPath = $"src/app/{rootPath}/shared/models/{parameterItemModelName}"; + var parameterItemModelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{parameterItemModelName}"; if (parameterItem.BindingSourceId == "body" && !File.Exists(parameterItemModelPath)) { parameterItem.Type = "any"; @@ -262,7 +277,7 @@ namespace Volo.Abp.Cli.Commands var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>'); var secondTypeModelName = secondType.PascalToKebabCase() + ".ts"; - var secondTypeModelPath = $"src/app/{rootPath}/shared/models/{secondTypeModelName}"; + var secondTypeModelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{secondTypeModelName}"; if (firstType == "List" && !File.Exists(secondTypeModelPath)) { secondType = "any"; @@ -306,7 +321,7 @@ namespace Volo.Abp.Cli.Commands } } - var modelIndex = CreateType(data, returnValueType, rootPath, modelIndexList); + var modelIndex = CreateType(data, returnValueType, rootPath, modelIndexList, controllerPathName); if (!string.IsNullOrWhiteSpace(modelIndex)) { @@ -359,26 +374,31 @@ namespace Volo.Abp.Cli.Commands serviceFileText.AppendLine("}"); serviceFileText.Replace("[controllerName]", controllerName); - File.WriteAllText($"src/app/{rootPath}/shared/services/{controllerServiceName}", serviceFileText.ToString()); - } + File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/services/{controllerServiceName}", serviceFileText.ToString()); - var serviceIndexFileText = new StringBuilder(); - foreach (var serviceIndexItem in serviceIndexList.Distinct()) - { - serviceIndexFileText.AppendLine($"export * from './{serviceIndexItem}';"); - } - File.WriteAllText($"src/app/{rootPath}/shared/services/index.ts", serviceIndexFileText.ToString()); + var serviceIndexFileText = new StringBuilder(); - var modelIndexFileText = new StringBuilder(); + foreach (var serviceIndexItem in serviceIndexList.Distinct()) + { + serviceIndexFileText.AppendLine($"export * from './{serviceIndexItem}';"); + } - foreach (var modelIndexItem in modelIndexList.Distinct()) - { - modelIndexFileText.AppendLine($"export * from './{modelIndexItem}';"); - } + File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/services/index.ts", serviceIndexFileText.ToString()); + + if (modelIndexList.Count > 0) + { + var modelIndexFileText = new StringBuilder(); + + foreach (var modelIndexItem in modelIndexList.Distinct()) + { + modelIndexFileText.AppendLine($"export * from './{modelIndexItem}';"); + } - File.WriteAllText($"src/app/{rootPath}/shared/models/index.ts", modelIndexFileText.ToString()); + File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/models/index.ts", modelIndexFileText.ToString()); + } + } } Logger.LogInformation("Completed!"); @@ -415,7 +435,7 @@ namespace Volo.Abp.Cli.Commands return moduleList; } - private static string CreateType(JObject data, string returnValueType, string rootPath, List modelIndexList) + private static string CreateType(JObject data, string returnValueType, string rootPath, List modelIndexList, string controllerPathName) { var type = data["types"][returnValueType]; @@ -425,30 +445,45 @@ namespace Volo.Abp.Cli.Commands } 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.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") + ) { - return null; + if (returnValueType.Contains("<")) + { + returnValueType = returnValueType.Split('<')[1].Split('>')[0]; + 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") + ) + { + return null; + } + } + else + { + return null; + } } var typeNameSplit = returnValueType.Split("."); var typeName = typeNameSplit[typeNameSplit.Length - 1]; - if (typeName.Contains("HttpStatusCode")) - { - - } - var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?", "").PascalToKebabCase() + ".ts"; - var path = $"src/app/{rootPath}/shared/models/{typeModelName}"; + var path = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeModelName}"; var modelFileText = new StringBuilder(); @@ -481,7 +516,7 @@ namespace Volo.Abp.Cli.Commands modelFileText.AppendLine($"import {{ {baseTypeName} }} from '{baseTypeKebabCase}';"); extends = "extends " + (!string.IsNullOrWhiteSpace(customBaseTypeName) ? customBaseTypeName : baseTypeName); - var modelIndex = CreateType(data, baseType, rootPath, modelIndexList); + var modelIndex = CreateType(data, baseType, rootPath, modelIndexList, controllerPathName); if (!string.IsNullOrWhiteSpace(modelIndex)) { modelIndexList.Add(modelIndex); @@ -514,7 +549,7 @@ namespace Volo.Abp.Cli.Commands propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1)); var typeSimple = (string)property["typeSimple"]; - var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList); + var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList, controllerPathName); if (typeSimple.IndexOf("[") > -1 && typeSimple.IndexOf("]") > -1) { @@ -547,7 +582,7 @@ namespace Volo.Abp.Cli.Commands ) { var typeSimpleModelName = typeSimple.PascalToKebabCase() + ".ts"; - var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}"; + var modelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeSimpleModelName}"; if (!File.Exists(modelPath)) { typeSimple = "any" + (typeSimple.Contains("[]") ? "[]" : ""); @@ -561,10 +596,18 @@ namespace Volo.Abp.Cli.Commands if (!string.IsNullOrWhiteSpace(modelIndex)) { + var from = "../models"; var propertyTypeSplit = ((string)property["type"]).Split("."); var propertyType = propertyTypeSplit[propertyTypeSplit.Length - 1]; + + var propertyTypeKebabCase = propertyType.PascalToKebabCase(); + if (File.Exists($"{outputPrefix}/{rootPath}/{controllerPathName}/models/{propertyTypeKebabCase}.ts")) + { + from = "./" + propertyTypeKebabCase; + } + modelFileText.Insert(0, ""); - modelFileText.Insert(0, $"import {{ {propertyType} }} from '../models';"); + modelFileText.Insert(0, $"import {{ {propertyType} }} from '{from}';"); modelFileText.Insert(0, ""); modelIndexList.Add(modelIndex); } @@ -608,7 +651,7 @@ namespace Volo.Abp.Cli.Commands modelFileText.AppendLine("}"); } - File.WriteAllText($"src/app/{rootPath}/shared/models/{typeModelName}", modelFileText.ToString()); + File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeModelName}", modelFileText.ToString()); return typeModelName.Replace(".ts", ""); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs index f7f76cda59..ccc0b0e62d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs @@ -199,7 +199,6 @@ namespace Volo.Abp.Cli.ProjectBuilding } catch (Exception ex) { - Console.WriteLine("Error occured while getting the NuGet version from {0} : {1}", url, ex.Message); return null; } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs index 15361ece62..5f4d64f512 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; @@ -24,13 +26,18 @@ namespace Volo.Abp.DependencyInjection return; } - var serviceTypes = ExposedServiceExplorer.GetExposedServices(type); + var exposedServiceTypes = ExposedServiceExplorer.GetExposedServices(type); - TriggerServiceExposing(services, type, serviceTypes); + TriggerServiceExposing(services, type, exposedServiceTypes); - foreach (var serviceType in serviceTypes) + foreach (var exposedServiceType in exposedServiceTypes) { - var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value); + var serviceDescriptor = CreateServiceDescriptor( + type, + exposedServiceType, + exposedServiceTypes, + lifeTime.Value + ); if (dependencyAttribute?.ReplaceServices == true) { @@ -46,7 +53,63 @@ namespace Volo.Abp.DependencyInjection } } } - + + protected virtual ServiceDescriptor CreateServiceDescriptor( + Type implementationType, + Type exposingServiceType, + List allExposingServiceTypes, + ServiceLifetime lifeTime) + { + if (lifeTime.IsIn(ServiceLifetime.Singleton, ServiceLifetime.Scoped)) + { + var redirectedType = GetRedirectedTypeOrNull( + implementationType, + exposingServiceType, + allExposingServiceTypes + ); + + if (redirectedType != null) + { + return ServiceDescriptor.Describe( + exposingServiceType, + provider => provider.GetService(redirectedType), + lifeTime + ); + } + } + + return ServiceDescriptor.Describe( + exposingServiceType, + implementationType, + lifeTime + ); + } + + protected virtual Type GetRedirectedTypeOrNull( + Type implementationType, + Type exposingServiceType, + List allExposingServiceTypes) + { + if (allExposingServiceTypes.Count < 2) + { + return null; + } + + if (exposingServiceType == implementationType) + { + return null; + } + + if (allExposingServiceTypes.Contains(implementationType)) + { + return implementationType; + } + + return allExposingServiceTypes.FirstOrDefault( + t => t != exposingServiceType && exposingServiceType.IsAssignableFrom(t) + ); + } + protected virtual DependencyAttribute GetDependencyAttributeOrNull(Type type) { return type.GetCustomAttribute(true); diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json new file mode 100644 index 0000000000..b96b58c2b3 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json @@ -0,0 +1,6 @@ +{ + "culture": "de", + "texts": { + "MaxResultCountExceededExceptionMessage": "{0} kann nicht mehr als {1} sein! Erhhen Sie {2}.{3} auf der Serverseite, um mehr Ergebnisse zu ermglichen." + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/nl.json b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/nl.json new file mode 100644 index 0000000000..cc4412cffc --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/nl.json @@ -0,0 +1,6 @@ +{ + "culture": "nl", + "texts": { + "MaxResultCountExceededExceptionMessage": "{0} kan niet meer dan {1} zijn! Vergroot {2}.{3} op de server om een groter resultaat toe te staan." + } +} diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json new file mode 100644 index 0000000000..8d6ae8c9ff --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json @@ -0,0 +1,23 @@ +{ + "culture": "de", + "texts": { + "DisplayName:Abp.Mailing.DefaultFromAddress": "Standard-Absenderadresse", + "DisplayName:Abp.Mailing.DefaultFromDisplayName": "Standard-Absendername", + "DisplayName:Abp.Mailing.Smtp.Host": "Host", + "DisplayName:Abp.Mailing.Smtp.Port": "Port", + "DisplayName:Abp.Mailing.Smtp.UserName": "Benutzername", + "DisplayName:Abp.Mailing.Smtp.Password": "Passwort", + "DisplayName:Abp.Mailing.Smtp.Domain": "Domain", + "DisplayName:Abp.Mailing.Smtp.EnableSsl": "SSL aktivieren", + "DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Standard-Anmeldeinformationen verwenden", + "Description:Abp.Mailing.DefaultFromAddress": "Die Standard-Absenderadresse", + "Description:Abp.Mailing.DefaultFromDisplayName": "Der Standard-Absendername", + "Description:Abp.Mailing.Smtp.Host": "Der Name oder die IP-Adresse des fr SMTP-Transaktionen verwendeten Hosts.", + "Description:Abp.Mailing.Smtp.Port": "Der fr SMTP-Transaktionen verwendete Port.", + "Description:Abp.Mailing.Smtp.UserName": "Benutzername, der mit den Anmeldedaten verknpft ist.", + "Description:Abp.Mailing.Smtp.Password": "Das Passwort fr den Benutzernamen, der mit den Anmeldeinformationen verknpft ist.", + "Description:Abp.Mailing.Smtp.Domain": "Die Domne oder der Computername, der die Anmeldeinformationen verifiziert.", + "Description:Abp.Mailing.Smtp.EnableSsl": "Bestimmt, ob der SmptClient Secure Sockets Layer (SSL) zur Verschlsselung der Verbindung verwendet.", + "Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Bestimmt, ob die DefaultCredentials mit Anfragen gesendet werden." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/nl.json b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/nl.json new file mode 100644 index 0000000000..923671e885 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/nl.json @@ -0,0 +1,23 @@ +{ + "culture": "nl", + "texts": { + "DisplayName:Abp.Mailing.DefaultFromAddress": "Standard vanaf adres", + "DisplayName:Abp.Mailing.DefaultFromDisplayName": "Standaard vanaf weergave naam", + "DisplayName:Abp.Mailing.Smtp.Host": "Host", + "DisplayName:Abp.Mailing.Smtp.Port": "Poort", + "DisplayName:Abp.Mailing.Smtp.UserName": "Gebruiker naam", + "DisplayName:Abp.Mailing.Smtp.Password": "wachtwoord", + "DisplayName:Abp.Mailing.Smtp.Domain": "Domein", + "DisplayName:Abp.Mailing.Smtp.EnableSsl": "SSL toestaan", + "DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Gebruik standaard inloggegevens", + "Description:Abp.Mailing.DefaultFromAddress": "Standard vanaf adres", + "Description:Abp.Mailing.DefaultFromDisplayName": "Standaard vanaf weergave naam", + "Description:Abp.Mailing.Smtp.Host": "De naam of het IP-adres van de host die wordt gebruikt voor SMTP-transacties.", + "Description:Abp.Mailing.Smtp.Port": "De poort die wordt gebruikt voor SMTP-transacties.", + "Description:Abp.Mailing.Smtp.UserName": "Gebruikersnaam gekoppeld aan de inloggegevens.", + "Description:Abp.Mailing.Smtp.Password": "Het wachtwoord voor de gebruikersnaam die bij de inloggegevens hoort.", + "Description:Abp.Mailing.Smtp.Domain": "Het domein of de computernaam die de inloggegevens verifieert.", + "Description:Abp.Mailing.Smtp.EnableSsl": "Of de SmtpClient Secure Sockets Layer (SSL) gebruikt om de verbinding te versleutelen.", + "Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Of de standaard inloggegevens worden verzonden met verzoeken." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/de.json b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/de.json new file mode 100644 index 0000000000..a00c0d5f8a --- /dev/null +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/de.json @@ -0,0 +1,7 @@ +{ + "culture": "de", + "texts": { + "DisplayName:Abp.Localization.DefaultLanguage": "Standardsprache", + "Description:Abp.Localization.DefaultLanguage": "Die Standardsprache der Anwendung." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/nl.json b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/nl.json new file mode 100644 index 0000000000..dd11c28ea0 --- /dev/null +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/nl.json @@ -0,0 +1,7 @@ +{ + "culture": "nl", + "texts": { + "DisplayName:Abp.Localization.DefaultLanguage": "Standaard taal", + "Description:Abp.Localization.DefaultLanguage": "De standaardtaal van de applicatie." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/de.json b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/de.json new file mode 100644 index 0000000000..170258b00c --- /dev/null +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/de.json @@ -0,0 +1,6 @@ +{ + "culture": "de", + "texts": { + "Menu:Administration": "Administration" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/nl.json b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/nl.json new file mode 100644 index 0000000000..279f79e1dc --- /dev/null +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/nl.json @@ -0,0 +1,6 @@ +{ + "culture": "nl", + "texts": { + "Menu:Administration": "Administratie" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json new file mode 100644 index 0000000000..a663b772be --- /dev/null +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json @@ -0,0 +1,63 @@ +{ + "culture": "de", + "texts": { + "InternalServerErrorMessage": "Whrend Ihrer Anfrage ist ein interner Fehler aufgetreten!", + "ValidationErrorMessage": "Ihre Anfrage ist nicht gltig!", + "ValidationNarrativeErrorMessageTitle": "Die folgenden Fehler wurden bei der Validierung entdeckt.", + "DefaultErrorMessage": "Ein Fehler ist aufgetreten!", + "DefaultErrorMessageDetail": "Es wurden keine Fehlerdetails vom Server gesendet.", + "DefaultErrorMessage401": "Sie sind nicht authentifiziert.", + "DefaultErrorMessage401Detail": "Sie sollten sich anmelden, um diese Operation durchzufhren.", + "DefaultErrorMessage403": "Sie sind nicht autorisiert!", + "DefaultErrorMessage403Detail": "Es ist Ihnen nicht erlaubt, diese Operation durchzufhren!", + "DefaultErrorMessage404": "Ressource nicht gefunden!", + "DefaultErrorMessage404Detail": "Die angeforderte Ressource konnte auf dem Server nicht gefunden werden!", + "EntityNotFoundErrorMessage": "Es gibt keine Entitt {0} mit id = {1}!", + "Languages": "Sprachen", + "Error": "Fehler", + "AreYouSure": "Sind Sie sicher?", + "Cancel": "Abbrechen", + "Yes": "Ja", + "No": "Nein", + "Ok": "Ok", + "Close": "Schlieen", + "Save": "Speichern", + "SavingWithThreeDot": "Speichere...", + "Actions": "Aktionen", + "Delete": "Lschen", + "Edit": "Bearbeiten", + "Refresh": "Aktualisieren", + "Language": "Sprache", + "LoadMore": "Mehr laden", + "ProcessingWithThreeDot": "Verarbeite...", + "LoadingWithThreeDot": "Lade...", + "Welcome": "Willkommen", + "Login": "Anmelden", + "Register": "Registrieren", + "Logout": "Abmelden", + "Submit": "Absenden", + "Back": "Zurck", + "PagerSearch": "Suchen", + "PagerNext": "Nchste", + "PagerPrevious": "Vorherige", + "PagerFirst": "Erste", + "PagerLast": "Letzte", + "PagerInfo": "Zeige _START_ bis _END_ von _TOTAL_ Eintrgen", + "PagerInfo{0}{1}{2}": "Zeige {0} bis {1} von {2} Eintrgen", + "PagerInfoEmpty": "Zeige 0 bis 0 von 0 Eintrgen", + "PagerInfoFiltered": "(gefiltert von _MAX_ Eintrgen insgesamt)", + "NoDataAvailableInDatatable": "Keine Daten verfgbar", + "PagerShowMenuEntries": "Zeige _MENU_ Eintrge", + "DatatableActionDropdownDefaultText": "Aktionen", + "ChangePassword": "Passwort ndern", + "PersonalInfo": "Mein Profil", + "AreYouSureYouWantToCancelEditingWarningMessage": "Sie haben ungespeicherte nderungen.", + "UnhandledException": "Unerwartete Ausnahme!", + "401Message": "Unauthorisiert", + "403Message": "Verboten", + "404Message": "Seite nicht gefunden", + "500Message": "Internet Server Fehler", + "GoHomePage": "Zur Startseite", + "GoBack": "Zurck" + } +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json new file mode 100644 index 0000000000..b44ea3794b --- /dev/null +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json @@ -0,0 +1,63 @@ +{ + "culture": "nl", + "texts": { + "InternalServerErrorMessage": "Er is een interne fout opgetreden tijdens uw verzoek!", + "ValidationErrorMessage": "Uw verzoek is niet geldig!", + "ValidationNarrativeErrorMessageTitle": "Tijdens de validatie zijn de volgende fouten gedetecteerd.", + "DefaultErrorMessage": "er is een fout opgetreden!", + "DefaultErrorMessageDetail": "Foutdetails niet verzonden door server.", + "DefaultErrorMessage401": "U bent niet geverifieerd!", + "DefaultErrorMessage401Detail": "U moet inloggen om deze bewerking uit te voeren.", + "DefaultErrorMessage403": "U bent niet geautoriseerd!", + "DefaultErrorMessage403Detail": "U mag deze bewerking niet uitvoeren!", + "DefaultErrorMessage404": "Bron niet gevonden!", + "DefaultErrorMessage404Detail": "De gevraagde bron kan niet worden gevonden op de server!", + "EntityNotFoundErrorMessage": "Er is geen entiteit {0} met id = {1}!", + "Languages": "Talen", + "Error": "Fout", + "AreYouSure": "Bent u zeker?", + "Cancel": "Annuleren", + "Yes": "Ja", + "No": "Nee", + "Ok": "Ok", + "Close": "Sluiten", + "Save": "Opslaan", + "SavingWithThreeDot": "Opslaan...", + "Actions": "Acties", + "Delete": "Verwijder", + "Edit": "Bewerk", + "Refresh": "Ververs", + "Language": "Taal", + "LoadMore": "Meer laden", + "ProcessingWithThreeDot": "Verwerken...", + "LoadingWithThreeDot": "Laden...", + "Welcome": "Welkom", + "Login": "Log in", + "Register": "Registreren", + "Logout": "Afmelden", + "Submit": "Verzenden", + "Back": "Terug", + "PagerSearch": "Zoeken", + "PagerNext": "Volgende", + "PagerPrevious": "Vorige", + "PagerFirst": "Eerste", + "PagerLast": "Laatste", + "PagerInfo": "Toont _START_ tot _END_ van _TOTAL_ vermeldingen", + "PagerInfo{0}{1}{2}": "{0} tot {1} van {2} vermeldingen weergeven", + "PagerInfoEmpty": "Toont 0 tot 0 van 0 vermeldingen", + "PagerInfoFiltered": "(gefilterd uit in totaal _MAX_ vermeldingen)", + "NoDataAvailableInDatatable": "Geen gegevens beschikbaar", + "PagerShowMenuEntries": "Toon _MENU_-vermeldingen", + "DatatableActionDropdownDefaultText": "Acties", + "ChangePassword": "Verander wachtwoord", + "PersonalInfo": "Mijn profiel", + "AreYouSureYouWantToCancelEditingWarningMessage": "U heeft nog niet-opgeslagen wijzigingen.", + "UnhandledException": "Onverwerkte uitzondering!", + "401Message": "Ongeautoriseerd", + "403Message": "Verboden", + "404Message": "Pagina niet gevonden", + "500Message": "Interne Server Fout", + "GoHomePage": "Ga naar de homepage", + "GoBack": "Ga terug" + } +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json index d202abe1eb..e3a0453e08 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json @@ -20,7 +20,7 @@ "ThisFieldIsNotValid.": "{0} není platný.", "ThisFieldIsNotAValidEmailAddress.": "V poli {0} není platný email.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Pole přijímá soubory pouze s následujícími koncovkami: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Vy poli musí být řežezec nebo řada o maximální délce '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Vy poli musí být řežezec nebo řada o maximální délce '{0}'.", "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "V poli musí být řežezec nebo řada o minimální délce '{0}'.", "ThisFieldIsNotAValidPhoneNumber.": "V poli není platné telefonní číslo.", "ThisFieldMustBeBetween{0}And{1}": "Pole musí být mezi {0} a {1}.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json new file mode 100644 index 0000000000..1e0e10716a --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json @@ -0,0 +1,34 @@ +{ + "culture": "de", + "texts": { + "'{0}' and '{1}' do not match.": "'{0}' und '{1}' stimmen nicht berein.", + "The {0} field is not a valid credit card number.": "Das Feld {0} ist keine gltige Kreditkartennummer.", + "{0} is not valid.": "{0} ist nicht gltig.", + "The {0} field is not a valid e-mail address.": "Das Feld {0} ist keine gltige E-Mail-Adresse.", + "The {0} field only accepts files with the following extensions: {1}": "Das Feld {0} akzeptiert nur Dateien mit den folgenden Erweiterungen: {1}", + "The field {0} must be a string or array type with a maximum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer maximalen Lnge von '{1}' sein.", + "The field {0} must be a string or array type with a minimum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer Mindestlnge von '{1}' sein.", + "The {0} field is not a valid phone number.": "Das Feld {0} ist keine gltige Telefonnummer.", + "The field {0} must be between {1} and {2}.": "Das Feld {0} muss zwischen {1} und {2} liegen.", + "The field {0} must match the regular expression '{1}'.": "Das Feld {0} muss dem regulren Ausdruck '{1}' entsprechen.", + "The {0} field is required.": "Das Feld {0} ist erforderlich.", + "The field {0} must be a string with a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer maximalen Lnge von {1} sein.", + "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer minimalen Lnge von {2} und einer maximalen Lnge von {1} sein.", + "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Das {0}-Feld ist keine gltige vollqualifizierte http-, https- oder ftp-URL.", + "The field {0} is invalid.": "Das Feld {0} ist ungltig.", + "ThisFieldIsNotAValidCreditCardNumber.": "Dieses Feld ist keine gltige Kreditkartennummer.", + "ThisFieldIsNotValid.": "Dieses Feld ist nicht gltig.", + "ThisFieldIsNotAValidEmailAddress.": "Dieses Feld ist keine gltige E-Mail-Adresse.", + "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Dieses Feld akzeptiert nur Dateien mit den folgenden Erweiterungen: {0}", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer maximalen Lnge von '{0}' sein.", + "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer Mindestlnge von '{0}' sein.", + "ThisFieldIsNotAValidPhoneNumber.": "Dieses Feld ist keine gltige Telefonnummer.", + "ThisFieldMustBeBetween{0}And{1}": "Dieses Feld muss zwischen {0} und {1} liegen.", + "ThisFieldMustMatchTheRegularExpression{0}": "Dieses Feld muss dem regulren Ausdruck '{0}' entsprechen.", + "ThisFieldIsRequired.": "Dieses Feld ist erforderlich.", + "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer maximalen Lnge von {0} sein.", + "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer Mindestlnge von '{0}' sein.", + "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Dieses Feld ist keine gltige vollqualifizierte http-, https- oder ftp-URL.", + "ThisFieldIsInvalid.": "Dieses Feld ist ungltig." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json index 74c6670e55..8ce00a7c5b 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json @@ -20,7 +20,7 @@ "ThisFieldIsNotValid.": "This field is not valid.", "ThisFieldIsNotAValidEmailAddress.": "This field is not a valid e-mail address.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "This field only accepts files with the following extensions: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "This field must be a string or array type with a maximum length of '{0}'.", "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.", "ThisFieldIsNotAValidPhoneNumber.": "This field is not a valid phone number.", "ThisFieldMustBeBetween{0}And{1}": "This field must be between {0} and {1}.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json index 1615d7db42..3f84a158aa 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json @@ -20,7 +20,7 @@ "ThisFieldIsNotValid.": "Este campo no es válido.", "ThisFieldIsNotAValidEmailAddress.": "Este campo no es una dirección de correo electrónico válida.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Este campo sólo acepta archivos con las siguientes extensiones: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Este campo debe ser una cadena o un array con una longitud máxima de '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Este campo debe ser una cadena o un array con una longitud máxima de '{0}'.", "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Este campo debe ser una cadena o un array con una longitud mínima de '{0}'.", "ThisFieldIsNotAValidPhoneNumber.": "Este campo no es un número de teléfono válido.", "ThisFieldMustBeBetween{0}And{1}": "Este campo debe tener un valor entre {0} y {1}.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json new file mode 100644 index 0000000000..ff0edfa54d --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json @@ -0,0 +1,34 @@ +{ + "culture": "nl", + "texts": { + "'{0}' and '{1}' do not match.": "'{0}' en '{1}' komen niet overeen.", + "The {0} field is not a valid credit card number.": "Het veld {0} is geen geldig krediet kaartnummer.", + "{0} is not valid.": "{0} is niet geldig.", + "The {0} field is not a valid e-mail address.": "Het veld {0} is geen geldig e-mailadres.", + "The {0} field only accepts files with the following extensions: {1}": "Het veld {0} accepteert alleen bestanden met de volgende extensies: {1}", + "The field {0} must be a string or array type with a maximum length of '{1}'.": "Het veld {0} moet een tekenreeks- of arraytype zijn met een maximale lengte van '{1}'.", + "The field {0} must be a string or array type with a minimum length of '{1}'.": "Het veld {0} moet een tekenreeks- of arraytype zijn met een minimale lengte van '{1}'.", + "The {0} field is not a valid phone number.": "Het veld {0} is geen geldig telefoonnummer.", + "The field {0} must be between {1} and {2}.": "Het veld {0} moet tussen {1} en {2} liggen.", + "The field {0} must match the regular expression '{1}'.": "Het veld {0} moet overeenkomen met de reguliere expressie '{1}'.", + "The {0} field is required.": "Het veld {0} is verplicht.", + "The field {0} must be a string with a maximum length of {1}.": "Het veld {0} moet een tekenreeks zijn met een maximale lengte van {1}.", + "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Het veld {0} moet een tekenreeks zijn met een minimale lengte van {2} en een maximale lengte van {1}.", + "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Het veld {0} is geen geldige, volledig gekwalificeerde http-, https- of ftp-URL.", + "The field {0} is invalid.": "Het veld {0} is ongeldig.", + "ThisFieldIsNotAValidCreditCardNumber.": "Dit veld is geen geldig krediet kaartnummer.", + "ThisFieldIsNotValid.": "Dir veld is ongeldig.", + "ThisFieldIsNotAValidEmailAddress.": "Dit veld is geen geldig e-mail adres.", + "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Dit veld accepteert alleen bestanden met de volgende extensies: {0}", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Dit veld moet een tekenreeks- of arraytype zijn met een maximale lengte van '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Dit veld moet een tekenreeks- of arraytype zijn met een minimale lengte van '{0}'.", + "ThisFieldIsNotAValidPhoneNumber.": "Dit veld is geen geldig telefoonnummer.", + "ThisFieldMustBeBetween{0}And{1}": "Dit veld moet tussen {0} en {1} liggen.", + "ThisFieldMustMatchTheRegularExpression{0}": "Dit veld moet overeenkomen met de reguliere expressie '{0}'.", + "ThisFieldIsRequired.": "Dit veld is verplicht.", + "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Dit veld moet een tekenreeks zijn met een maximale lengte van {0}.", + "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Dit veld moet een tekenreeks zijn met een minimale lengte van {1} en een maximale lengte van {0}.", + "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Dit veld is geen geldige, volledig gekwalificeerde http-, https- of ftp-URL.", + "ThisFieldIsInvalid.": "Dit veld is ongeldig." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json index 8d799a7730..2af525b373 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json @@ -20,7 +20,7 @@ "ThisFieldIsNotValid.": "Campo inválido.", "ThisFieldIsNotAValidEmailAddress.": "E-mail inválido.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Este campo só aceita arquivos com asseguintes extensões: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "This field must be a string or array type with a maximum length of '{0}'.", "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.", "ThisFieldIsNotAValidPhoneNumber.": "Número de telefone inválido.", "ThisFieldMustBeBetween{0}And{1}": "Este campo deve estar entre {0} e {1}.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json index d718dbbed8..877a3bb1dc 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json @@ -20,7 +20,7 @@ "ThisFieldIsNotValid.": "Значение в этом поле недействительно.", "ThisFieldIsNotAValidEmailAddress.": "Это поле не содержит действительный адрес электронной почты.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Вы можете загрузить файлы только следующих форматов: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Это поле должно иметь тип строки или массива с максимальной длиной '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Это поле должно иметь тип строки или массива с максимальной длиной '{0}'.", "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Это поле должно иметь тип строки или массива с минимальной длиной '{0}'.", "ThisFieldIsNotAValidPhoneNumber.": "Это поле не содержит действительный номер телефона.", "ThisFieldMustBeBetween{0}And{1}": "Это поле должно быть между {0} и {1}.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json index a70a3cd329..caeb19eebe 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json @@ -20,7 +20,7 @@ "ThisFieldIsNotValid.": "Bu alan geçerli değil.", "ThisFieldIsNotAValidEmailAddress.": "Bu alan geçerli bir e-posta adresi olmalıdır.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Bu alan sadece şu uzantılarda dosyaları kabul eder: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Bu alan en fazla '{0}' uzunluğunda bir metin ya da dizi olmalıdır.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Bu alan en fazla '{0}' uzunluğunda bir metin ya da dizi olmalıdır.", "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Bu alan en az '{0}' uzunluğunda bir metin ya da dizi olmalıdır.", "ThisFieldIsNotAValidPhoneNumber.": "Bu alan geçerli bir telefon numarası olmalıdır.", "ThisFieldMustBeBetween{0}And{1}": "Bu alanın değeri {0} ile {1} arasında olmalıdır.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json index 88a6e93acf..83c7e3b133 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json @@ -20,7 +20,7 @@ "ThisFieldIsNotValid.": "验证未通过.", "ThisFieldIsNotAValidEmailAddress.": "字段不是有效的邮箱地址.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "字段只允许以下扩展名的文件: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "字段必须是最大长度为'{0}'的字符串或数组.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "字段必须是最大长度为'{0}'的字符串或数组.", "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "字段必须是最小长度为'{0}'的字符串或数组.", "ThisFieldIsNotAValidPhoneNumber.": "字段不是有效的手机号码.", "ThisFieldMustBeBetween{0}And{1}": "字段值必须在{0}和{1}范围内.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json index 2cd200151c..ec0fabd121 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json @@ -20,7 +20,7 @@ "ThisFieldIsNotValid.": "此驗證未通過.", "ThisFieldIsNotAValidEmailAddress.": "此欄位不是有效的郵箱地址.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "此欄位只允許以下副檔名的文件: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "此欄位必須是最大長度為'{0}'的字串或陣列.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "此欄位必須是最大長度為'{0}'的字串或陣列.", "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "此欄位必須是最小長度為'{0}'的字串或陣列.", "ThisFieldIsNotAValidPhoneNumber.": "此欄位不是有效的電話號碼.", "ThisFieldMustBeBetween{0}And{1}": "此欄位值必須在{0}和{1}範圍內.", diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DictionaryBasedFileProvider.cs b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DictionaryBasedFileProvider.cs index a8ea963f0e..bd2adc5a2b 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DictionaryBasedFileProvider.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DictionaryBasedFileProvider.cs @@ -11,7 +11,7 @@ namespace Volo.Abp.VirtualFileSystem public virtual IFileInfo GetFileInfo(string subpath) { - if (string.IsNullOrEmpty(subpath)) + if (subpath == null) { return new NotFoundFileInfo(subpath); } @@ -67,4 +67,4 @@ namespace Volo.Abp.VirtualFileSystem return subpath; } } -} \ 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 ea9c4daf54..03f851a7e3 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFilePathHelper.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFilePathHelper.cs @@ -10,6 +10,11 @@ namespace Volo.Abp.VirtualFileSystem public static string NormalizePath(string fullPath) { + if (fullPath.Equals("/", StringComparison.Ordinal)) + { + return string.Empty; + } + var fileName = fullPath; var extension = ""; @@ -29,7 +34,7 @@ namespace Volo.Abp.VirtualFileSystem return NormalizeChars(fileName) + extension; } - + private static string NormalizeChars(string fileName) { var folderParts = fileName.Replace(".", "/").Split("/"); @@ -42,4 +47,4 @@ namespace Volo.Abp.VirtualFileSystem return folderParts.Take(folderParts.Length - 1).Select(s => s.Replace("-", "_")).JoinAsString("/") + "/" + folderParts.Last(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json new file mode 100644 index 0000000000..e5b59a1915 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json @@ -0,0 +1,7 @@ +{ + "culture": "de", + "texts": { + "BirthDate": "Geburtsdatum", + "Value1": "Wert Eins" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/nl.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/nl.json new file mode 100644 index 0000000000..526e7203a7 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/nl.json @@ -0,0 +1,7 @@ +{ + "culture": "nl", + "texts": { + "BirthDate": "Geboortedatum", + "Value1": "Waarde een" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs index 680cb607f7..d6e9847cfc 100644 --- a/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Shouldly; -using Volo.Abp; using Volo.Abp.DependencyInjection; using Volo.Abp.Modularity; using Volo.Abp.Testing; @@ -63,6 +62,15 @@ namespace Microsoft.Extensions.DependencyInjection GetRequiredService().ProperyInjectedService.ShouldNotBeNull(); } + [Fact] + public void Singletons_Exposing_Multiple_Services_Should_Returns_The_Same_Instance() + { + var objectByInterfaceRef = GetRequiredService(); + var objectByClassRef = GetRequiredService(); + + ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue(); + } + public class MySingletonService : ISingletonDependency { public List TransientInstances { get; } @@ -116,6 +124,17 @@ namespace Microsoft.Extensions.DependencyInjection } } + public interface IMySingletonExposingMultipleServices + { + + } + + [ExposeServices(typeof(IMySingletonExposingMultipleServices), typeof(MySingletonExposingMultipleServices))] + public class MySingletonExposingMultipleServices : IMySingletonExposingMultipleServices, ISingletonDependency + { + + } + public class TestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) @@ -124,6 +143,7 @@ namespace Microsoft.Extensions.DependencyInjection context.Services.AddType(); context.Services.AddType(); context.Services.AddType(); + context.Services.AddType(); context.Services.AddTransient(typeof(GenericServiceWithPropertyInject<>)); context.Services.AddTransient(typeof(ConcreteGenericServiceWithPropertyInject)); } diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/de.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/de.json new file mode 100644 index 0000000000..504dff050c --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/de.json @@ -0,0 +1,6 @@ +{ + "culture": "de", + "texts": { + "hello": "Hallo" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/nl.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/nl.json new file mode 100644 index 0000000000..2a19df7473 --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/nl.json @@ -0,0 +1,6 @@ +{ + "culture": "nl", + "texts": { + "hello": "hallo" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs index 5608ca0a80..961f1ba0a4 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs @@ -73,6 +73,11 @@ namespace Volo.Abp.Localization _localizer["Car"].Value.ShouldBe("Auto"); } + using (CultureHelper.Use("de")) + { + _localizer["Car"].Value.ShouldBe("Auto"); + } + } [Fact] @@ -98,6 +103,11 @@ namespace Volo.Abp.Localization _localizer["SeeYou"].Value.ShouldBe("Nos vemos"); } + using (CultureHelper.Use("de")) + { + _localizer["SeeYou"].Value.ShouldBe("Bis bald"); + } + } [Fact] diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/de.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/de.json new file mode 100644 index 0000000000..1412b467f8 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/de.json @@ -0,0 +1,7 @@ +{ + "culture": "de", + "texts": { + "USA": "Vereinigte Staaten von Amerika", + "Brazil": "Brasilien" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json new file mode 100644 index 0000000000..849d1b0df6 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json @@ -0,0 +1,7 @@ +{ + "culture": "nl", + "texts": { + "USA": "Verenigde Staten van Amerika", + "Brazil": "Brazilië" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/de.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/de.json new file mode 100644 index 0000000000..22e395eb77 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/de.json @@ -0,0 +1,7 @@ +{ + "culture": "de", + "texts": { + "ThisFieldIsRequired": "Dieses Feld ist ein Pflichtfeld", + "MaxLenghtErrorMessage": "Die Länge dieses Feldes kann maximal '{0}'-Zeichen betragen" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json new file mode 100644 index 0000000000..5875cf7a12 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json @@ -0,0 +1,7 @@ +{ + "culture": "nl", + "texts": { + "ThisFieldIsRequired": "Dit veld is verplicht", + "MaxLenghtErrorMessage": "Dit veld mag maximaal '{0}' tekens bevatten" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json new file mode 100644 index 0000000000..4ef1655764 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json @@ -0,0 +1,11 @@ +{ + "culture": "de", + "texts": { + "Hello {0}.": "Hallo {0}.", + "Car": "Auto", + "CarPlural": "Autos", + "MaxLenghtErrorMessage": "Die Länge dieses Feldes kann maximal '{0}'-Zeichen betragen", + "Universe": "Universum", + "FortyTwo": "Zweiundvierzig" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json new file mode 100644 index 0000000000..0e0ba67ef6 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json @@ -0,0 +1,11 @@ +{ + "culture": "nl", + "texts": { + "Hello {0}.": "Hallo {0}.", + "Car": "Auto", + "CarPlural": "Auto's", + "MaxLenghtErrorMessage": "De lengte van dit veld mag maximaal '{0}' tekens zijn", + "Universe": "Universum", + "FortyTwo": "Tweeënveertig" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/de.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/de.json new file mode 100644 index 0000000000..fe1216d361 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/de.json @@ -0,0 +1,8 @@ +{ + "culture": "de", + "texts": { + "Hello {0}.": "Hallo {0}.", + "Car": "Auto", + "SeeYou": "Bis bald" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json new file mode 100644 index 0000000000..7e7354d11b --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json @@ -0,0 +1,6 @@ +{ + "culture": "nl", + "texts": { + "SeeYou": "Tot ziens" + } +} \ 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 3d69b87b56..2243d9a11a 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,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using System.Text; using Microsoft.Extensions.DependencyInjection; @@ -47,6 +48,21 @@ namespace Volo.Abp.VirtualFileSystem contentList.ShouldContain(x => x.Name == "jquery-3-1-1-min.js"); } + [Theory] + [InlineData("/")] + [InlineData("")] + public void Should_Define_And_Get_Embedded_Root_Directory_Contents(string path) + { + //Act + var contents = _virtualFileProvider.GetDirectoryContents(path); + + //Assert + contents.Exists.ShouldNotBeNull(); + + var contentList = contents.ToList(); + contentList.ShouldContain(x => x.Name == "js"); + } + [DependsOn(typeof(AbpVirtualFileSystemModule))] public class TestModule : AbpModule { diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json new file mode 100644 index 0000000000..935fff56a6 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json @@ -0,0 +1,45 @@ +{ + "culture": "de", + "texts": { + "UserName": "Benutzername", + "EmailAddress": "E-Mail-Adresse", + "UserNameOrEmailAddress": "Benutzername oder E-Mail-Adresse", + "Password": "Passwort", + "RememberMe": "Angemeldet bleiben", + "UseAnotherServiceToLogin": "Einen anderen Dienst zum Anmelden verwenden", + "UserLockedOutMessage": "Das Benutzerkonto wurde aufgrund fehlgeschlagener Anmeldeversuche gesperrt. Bitte warten Sie eine Weile und versuchen Sie es erneut.", + "InvalidUserNameOrPassword": "Ungltiger Benutzername oder Passwort!", + "LoginIsNotAllowed": "Sie drfen sich nicht anmelden! Sie mssen Ihre E-Mail/Telefonnummer besttigen.", + "SelfRegistrationDisabledMessage": "Die Selbstregistrierung ist fr diese Anwendung deaktiviert. Bitte wenden Sie sich an den Anwendungsadministrator, um einen neuen Benutzer zu registrieren.", + "LocalLoginDisabledMessage": "Die lokale Anmeldung ist fr diese Anwendung deaktiviert.", + "Login": "Anmelden", + "Cancel": "Abbrechen", + "Register": "Registrieren", + "AreYouANewUser": "Neuer Benutzer?", + "AlreadyRegistered": "Bereits registriert?", + "InvalidLoginRequest": "Ungltige Login-Anfrage", + "ThereAreNoLoginSchemesConfiguredForThisClient": "Es sind keine Anmeldeschemata fr diesen Client konfiguriert.", + "LogInUsingYourProviderAccount": "Melden Sie sich mit Ihrem {0}-Konto an", + "DisplayName:CurrentPassword": "Aktuelles Passwort", + "DisplayName:NewPassword": "Neues Passwort", + "DisplayName:NewPasswordConfirm": "Neues Passwort besttigen", + "PasswordChangedMessage": "Ihr Passwort wurde erfolgreich gendert.", + "DisplayName:UserName": "Benutzername", + "DisplayName:Email": "E-Mail", + "DisplayName:Name": "Name", + "DisplayName:Surname": "Nachname", + "DisplayName:Password": "Passwort", + "DisplayName:EmailAddress": "E-Mail-Adresse", + "DisplayName:PhoneNumber": "Telefonnummer", + "PersonalSettings": "Persnliche Einstellungen", + "PersonalSettingsSaved": "Persnliche Einstellungen gespeichert", + "PasswordChanged": "Passwort gendert", + "NewPasswordConfirmFailed": "Bitte besttigen Sie das neue Passwort.", + "Manage": "Verwalten", + "ManageYourProfile": "Ihr profil verwalten", + "DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Ist die Selbstregistrierung aktiviert", + "Description:Abp.Account.IsSelfRegistrationEnabled": "Gibt an, ob ein Benutzer das Konto selbst registrieren kann.", + "DisplayName:Abp.Account.EnableLocalLogin": "Authentifizierung mit einem lokalen Konto", + "Description:Abp.Account.EnableLocalLogin": "Gibt an, ob der Server Benutzern die Authentifizierung mit einem lokalen Konto erlaubt." + } +} diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/nl.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/nl.json new file mode 100644 index 0000000000..64a1a70124 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/nl.json @@ -0,0 +1,45 @@ +{ + "culture": "nl", + "texts": { + "UserName": "Gebruikersnaam", + "EmailAddress": "E-mailadres", + "UserNameOrEmailAddress": "Gebruikersnaam of e-mail adres", + "Password": "Wachtwoord", + "RememberMe": "Herinner me", + "UseAnotherServiceToLogin": "Gebruik een andere dienst om in te loggen", + "UserLockedOutMessage": "Het gebruikersaccount is geblokkeerd vanwege ongeldige inlogpogingen. Wacht even en probeer het opnieuw.", + "InvalidUserNameOrPassword": "Ongeldige gebruikersnaam of wachtwoord!", + "LoginIsNotAllowed": "U mag niet inloggen! U moet uw e-mailadres / telefoonnummer bevestigen.", + "SelfRegistrationDisabledMessage": "Zelfregistratie is uitgeschakeld voor deze applicatie. Neem contact op met de applicatiebeheerder om een nieuwe gebruiker te registreren.", + "LocalLoginDisabledMessage": "Lokale aanmelding is uitgeschakeld voor deze applicatie.", + "Login": "Log in", + "Cancel": "Annuleer", + "Register": "Registreer", + "AreYouANewUser": "Bent u een nieuwe gebruiker?", + "AlreadyRegistered": "Al geregistreerd?", + "InvalidLoginRequest": "Ongeldig inlogverzoek", + "ThereAreNoLoginSchemesConfiguredForThisClient": "Er zijn geen aanmeldingsschema's geconfigureerd voor deze client.", + "LogInUsingYourProviderAccount": "Log in met uw {0} -account", + "DisplayName:CurrentPassword": "Huidig wachtwoord", + "DisplayName:NewPassword": "Nieuw wachtwoord", + "DisplayName:NewPasswordConfirm": "Bevestig nieuw wachtwoord", + "PasswordChangedMessage": "Uw wachtwoord is met succes veranderd.", + "DisplayName:UserName": "Gebruikersnaam", + "DisplayName:Email": "E-mail", + "DisplayName:Name": "Naam", + "DisplayName:Surname": "Achternaam", + "DisplayName:Password": "Wachtwoord", + "DisplayName:EmailAddress": "E-mail adres", + "DisplayName:PhoneNumber": "Telefoonnummer", + "PersonalSettings": "Persoonlijke instellingen", + "PersonalSettingsSaved": "Persoonlijke instellingen opgeslagen", + "PasswordChanged": "wachtwoord veranderd", + "NewPasswordConfirmFailed": "Bevestig het nieuwe wachtwoord a.u.b..", + "Manage": "Beheer", + "ManageYourProfile": "Beheer uw profiel", + "DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Is zelfregistratie ingeschakeld", + "Description:Abp.Account.IsSelfRegistrationEnabled": "Of een gebruiker het account zelf kan registreren.", + "DisplayName:Abp.Account.EnableLocalLogin": "Verifieer met een lokaal account", + "Description:Abp.Account.EnableLocalLogin": "Geeft aan of de server gebruikers toestaat zich te verifiëren met een lokaal account." + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs index 1cfc96091a..64fbdd40d1 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs @@ -18,7 +18,7 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers [RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] [Controller] [ControllerName("Login")] - [Area("Account")] + [Area("account")] [Route("api/account")] public class AccountController : AbpController { diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/de.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/de.json new file mode 100644 index 0000000000..9455b3cd44 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/de.json @@ -0,0 +1,14 @@ +{ + "culture": "de", + "texts": { + "Permission:Blogging": "Blog", + "Permission:Blogs": "Blogs", + "Permission:Posts": "Beitrge", + "Permission:Tags": "Tags", + "Permission:Comments": "Kommentare", + "Permission:Management": "Verwaltung", + "Permission:Edit": "Bearbeiten", + "Permission:Create": "Erstellen", + "Permission:Delete": "Lschen" + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/nl.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/nl.json new file mode 100644 index 0000000000..7e34f16b79 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/nl.json @@ -0,0 +1,14 @@ +{ + "culture": "nl", + "texts": { + "Permission:Blogging": "Blog", + "Permission:Blogs": "Blogs", + "Permission:Posts": "Posts", + "Permission:Tags": "Tags", + "Permission:Comments": "Kommentaar", + "Permission:Management": "Beheer", + "Permission:Edit": "Bewerk", + "Permission:Create": "Maak aan", + "Permission:Delete": "Verwijder" + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json new file mode 100644 index 0000000000..6489396692 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json @@ -0,0 +1,49 @@ +{ + "culture": "de", + "texts": { + "Menu:Blogs": "Blogs", + "Menu:BlogManagement": "Blog-Verwaltung", + "Title": "Titel", + "Delete": "Lschen", + "Reply": "Antwort", + "ReplyTo": "Antwort auf {0}", + "ContinueReading": "Weiterlesen", + "DaysAgo": "vor {0} Tagen", + "YearsAgo": "vor {0} Jahren", + "MonthsAgo": "vor {0} Monaten", + "WeeksAgo": "vor {0} Wochen", + "MinutesAgo": "vor {0} Minuten", + "SecondsAgo": "vor {0} Sekunden", + "HoursAgo": "vor {0} Stunden", + "Now": "jetzt", + "Content": "Inhalt", + "SeeAll": "Alle anzeigen", + "PopularTags": "Beliebte Tags", + "WiewsWithCount": "{0} Aufrufe", + "LastPosts": "Letzte Beitrge", + "LeaveComment": "Kommentar hinterlassen", + "TagsInThisArticle": "Tags in diesem Artikel", + "Posts": "Beitrge", + "Edit": "Bearbeiten", + "BLOG": "BLOG", + "CommentDeletionWarningMessage": "Kommentar wird gelscht.", + "PostDeletionWarningMessage": "Beitrag wird gelscht.", + "BlogDeletionWarningMessage": "Blog wird gelscht.", + "AreYouSure": "Sind Sie sicher?", + "CommentWithCount": "{0} Kommentare", + "Comment": "Kommentar", + "ShareOnTwitter": "Auf Twitter teilen", + "CoverImage": "Titelbild", + "CreateANewPost": "Neuen Beitrag erstellen", + "CreateANewBlog": "Neuen Blog erstellen", + "WhatIsNew": "Was ist neu?", + "Name": "Name", + "ShortName": "Kurzname", + "CreationTime": "Erstellungszeit", + "Description": "Beschreibung", + "Blogs": "Blogs", + "Tags": "Tags", + "ShareOn": "Teilen auf", + "TitleLengthWarning": "Halten Sie Ihren Titel unter 60 Zeichen, um SEO-freundlich zu sein!" + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json new file mode 100644 index 0000000000..6548e4c0a8 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json @@ -0,0 +1,49 @@ +{ + "culture": "nl", + "texts": { + "Menu:Blogs": "Blogs", + "Menu:BlogManagement": "Blog Beheer", + "Title": "Titel", + "Delete": "Verwijder", + "Reply": "Antwoord", + "ReplyTo": "Antwoord aan {0}", + "ContinueReading": "Lees verder", + "DaysAgo": "{0} dagen geleden", + "YearsAgo": "{0} jaar geleden", + "MonthsAgo": "{0} maanden geleden", + "WeeksAgo": "{0} weken geleden", + "MinutesAgo": "{0} minuten geleden", + "SecondsAgo": "{0} seconden geleden", + "HoursAgo": "{0} uur geleden", + "Now": "nu", + "Content": "Inhoud", + "SeeAll": "Alles zien", + "PopularTags": "Populaire tags", + "WiewsWithCount": "{0} keer bekeken", + "LastPosts": "Laatste berichten", + "LeaveComment": "Laat commentaar achter", + "TagsInThisArticle": "Tags in dit artikel", + "Posts": "Berichten", + "Edit": "Bewerk", + "BLOG": "BLOG", + "CommentDeletionWarningMessage": "Reactie wordt verwijderd.", + "PostDeletionWarningMessage": "Berich wordt verwijderd.", + "BlogDeletionWarningMessage": "Blog wordt verwijderd.", + "AreYouSure": "Weet u het zeker?", + "CommentWithCount": "{0} reacties", + "Comment": "Reactie", + "ShareOnTwitter": "Delen op Twitter", + "CoverImage": "Omslagfoto", + "CreateANewPost": "Maak een nieuw bericht", + "CreateANewBlog": "Maak een nieuwe Blog", + "WhatIsNew": "Wat is nieuw?", + "Name": "Naam", + "ShortName": "Korte naam", + "CreationTime": "Creatie tijd", + "Description": "Beschrijving", + "Blogs": "Blogs", + "Tags": "Tags", + "ShareOn": "Delen op", + "TitleLengthWarning": "Houd uw titel kleiner dan 60 tekens om SEO-vriendelijk te zijn!" + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs b/modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs new file mode 100644 index 0000000000..8c1b1fbf7d --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Volo.Blogging +{ + public class BloggingTwitterOptions + { + public string Site { get; set; } + } +} diff --git a/modules/blogging/src/Volo.Blogging.Web/BloggingUrlOptions.cs b/modules/blogging/src/Volo.Blogging.Web/BloggingUrlOptions.cs index 0c56d1c566..619585d359 100644 --- a/modules/blogging/src/Volo.Blogging.Web/BloggingUrlOptions.cs +++ b/modules/blogging/src/Volo.Blogging.Web/BloggingUrlOptions.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Volo.Blogging { diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml index e674ae1c3d..63cfbf8a8e 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml @@ -2,16 +2,25 @@ @inherits Volo.Blogging.Pages.Blog.BloggingPage @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Http.Extensions +@using Microsoft.Extensions.Options @using Volo.Abp.Users @using Volo.Blogging @using Volo.Blogging.Pages.Blog.Posts @using Volo.Blogging.Areas.Blog.Helpers.TagHelpers @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs @inject IAuthorizationService Authorization +@inject IOptionsSnapshot twitterOptions @model DetailModel @{ ViewBag.Title = Model.Post.Title; ViewBag.Description = Model.Post.Description; + + ViewBag.TwitterCard = "summary_large_image"; + ViewBag.TwitterSite = string.IsNullOrWhiteSpace(twitterOptions.Value.Site) ? "" : twitterOptions.Value.Site; + ViewBag.TwitterTitle = Model.Post.Title; + ViewBag.TwitterDescription = Model.Post.Description; + ViewBag.TwitterImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}"; + var hasCommentingPermission = CurrentUser.IsAuthenticated; //TODO: Apply real policy! } @section scripts { diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml index fe5207cb52..5a8f9bd694 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml @@ -105,19 +105,23 @@

@post.Title

-
-
@@ -161,24 +165,26 @@

Continue Reading → -
-