diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md index de89920946..1bceda951f 100644 --- a/docs/en/Authorization.md +++ b/docs/en/Authorization.md @@ -204,6 +204,10 @@ If you define and register a policy to the ASP.NET Core authorization system wit See [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) document to learn how to define a custom policy. +### Changing Permission Definitions of a Depended Module + +A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. + ## IAuthorizationService ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization. diff --git a/docs/en/Customizing-Application-Modules-Guide.md b/docs/en/Customizing-Application-Modules-Guide.md index 9989c6fc09..7f913f366a 100644 --- a/docs/en/Customizing-Application-Modules-Guide.md +++ b/docs/en/Customizing-Application-Modules-Guide.md @@ -53,3 +53,10 @@ This section suggests some approaches if you decided to use pre-built applicatio * [Overriding Services](Customizing-Application-Modules-Overriding-Services.md) * [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md) +### See Also + +Also, see the following documents: + +* See [the localization document](Localization.md) to learn how to extend existing localization resources. +* See [the settings document](Settings.md) to learn how to change setting definitions of a depended module. +* See [the authorization document](Authorization.md) to learn how to change permission definitions of a depended module. \ No newline at end of file diff --git a/docs/en/IdentityServer-Integration.md b/docs/en/IdentityServer-Integration.md new file mode 100644 index 0000000000..da82d4bc6a --- /dev/null +++ b/docs/en/IdentityServer-Integration.md @@ -0,0 +1,3 @@ +# IdentityServer Integration + +TODO \ No newline at end of file diff --git a/docs/en/Modules/Blogging.md b/docs/en/Modules/Blogging.md index 0d2ab35c21..32d2e023ea 100644 --- a/docs/en/Modules/Blogging.md +++ b/docs/en/Modules/Blogging.md @@ -1,3 +1,3 @@ -# IdentityServer Module +# Blogging Module TODO \ No newline at end of file diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md index afcc1a9dd7..db752ceb74 100644 --- a/docs/en/Modules/Docs.md +++ b/docs/en/Modules/Docs.md @@ -20,23 +20,25 @@ When you use GitHub to store your docs, Docs Module supports versioning. If you > Docs module follows the [module architecture best practices](../Best-Practices/Module-Architecture.md) guide. +## Installation +### 1- Download -## Installation +If you do not have an existing ABP project, this step shows you how to create a new project from [abp.io](https://abp.io) to add the Docs Module. If you already have an ABP project, you can skip this step. -### 1- Download +It is recommended to use ABP CLI to create new projects. Use the following command: -If you do not have an existing ABP project, this step shows you how to create a new project from [abp.io](https://abp.io) to add the Docs Module. If you already have an ABP project, you can skip this step. +`abp new Acme.MyProject` -Navigate to https://abp.io/Templates. Enter your project name as `Acme.MyProject`, select `ASP.NET Core Mvc Application` and select `Entity Framework Core` for the database provider. +You can also navigate to https://abp.io/get-started. Enter your project name as `Acme.MyProject`, other use default options. -Note that this document covers `Entity Framework Core` provider but you can also select `MongoDB` as your database provider. +Note that this document covers `Entity Framework Core` provider but you can also select `MongoDB` as your database provider. ![Create new project](../images/docs-module_download-new-abp-project.png) ### 2- Running The Empty Application -After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Domain `, `EntityFrameworkCore` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**. +After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `EntityFrameworkCore.DbMigations`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**. ![Create a new project](../images/docs-module_solution-explorer.png) @@ -45,16 +47,14 @@ The database connection string is located in `appsettings.json` of your `Acme.My ```json { "ConnectionStrings": { - "Default": "Server=localhost;Database=MyProject;Trusted_Connection=True;MultipleActiveResultSets=true" + "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProject;Trusted_Connection=True;MultipleActiveResultSets=true" } } ``` +Run `Acme.MyProject.DbMigrator` project, it will be responsible for applying database migration and seed data. The database `MyProject` will be created in your database server. - -Open `Package Manager Console` in the Visual Studio and choose `src\Acme.MyProject.EntityFrameworkCore` as the default project. Run `Update-Database` command to create your new database. The database `MyProject` will be created in your database server. - -Now an empty ABP project has been created! You can now run your project and see the empty website. +Now an empty ABP project has been created! You can now run your project and see the empty website. To login your website enter `admin` as the username and `1q2w3E*` as the password. @@ -62,36 +62,27 @@ To login your website enter `admin` as the username and `1q2w3E*` as the passwor Docs module packages are hosted on NuGet. There are 4 packages that needs be to installed to your application. Each package has to be installed to the relevant project. -* [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) needs to be referenced to `Acme.MyProject.Domain` project. +It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: - * Edit `Acme.MyProject.Domain.csproj`file and add the below line to as a reference. Note that you need to change version (v0.9.0) to the latest. +`abp add-module Volo.Docs` - ```csharp - - ``` -* [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) needs to be referenced to `Acme.MyProject.EntityFrameworkCore` project. +Or you can also manually install nuget package to each project: - - Edit `Acme.MyProject.EntityFrameworkCore.csproj`file and add the below line to as a reference. Note that you need to change version (v0.9.0) to the latest. +* Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project. - ```csharp - - ``` -* [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) needs to be referenced to `Acme.MyProject.Application` project. + `Install-Package Volo.Docs.Domain` - * Edit `Acme.MyProject.Application.csproj`file and add the below line to as a reference. Note that you need to change version (v0.9.0) to the latest. +* Install [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) nuget package to `Acme.MyProject.EntityFrameworkCore` project. - ```csharp - - ``` -* [Volo.Docs.Web ](https://www.nuget.org/packages/Volo.Docs.Web/)needs to be referenced to `Acme.MyProject.Web` project. + `Install-Package Volo.Docs.EntityFrameworkCore` - - Edit `Acme.MyProject.Web.csproj`file and add the below line to as a reference. Note that you need to change version (v0.9.0) to the latest. +* Install [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) nuget package to `Acme.MyProject.Application` project. - ```csharp - - ``` + `Install-Package Volo.Docs.Application` +* Install [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Web` project. + `Install-Package Volo.Docs.Web` ### 3- Adding Module Dependencies @@ -132,7 +123,6 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a } ``` - * Open `MyProjectApplicationModule.cs`and add `typeof(DocsApplicationModule)` as shown below; ```csharp @@ -157,7 +147,6 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a } ``` - * Open `MyProjectWebModule.cs`and add `typeof(DocsWebModule)` as shown below; ```csharp @@ -176,43 +165,62 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a } ``` - - ### 4- Database Integration #### 4.1- Entity Framework Integration -If you choose Entity Framework as your database provider, you need to configure the Docs Module in your DbContext. To do this; +If you choose Entity Framework as your database provider, you need to configure the Docs Module. To do this; -- Open `MyProjectDbContext.cs` and add `modelBuilder.ConfigureDocs()` to the `OnModelCreating()` +- Open `MyProjectMigrationsDbContext.cs` and add `builder.ConfigureDocs()` to the `OnModelCreating()`. ```csharp - [ConnectionStringName("Default")] - public class MyProjectDbContext : AbpDbContext - { - public MyProjectDbContext(DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - //... - modelBuilder.ConfigureDocs(); - } - } + public class MyProjectMigrationsDbContext : AbpDbContext + { + public MyProjectMigrationsDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureIdentity(); + builder.ConfigureIdentityServer(); + builder.ConfigureFeatureManagement(); + builder.ConfigureTenantManagement(); + builder.ConfigureDocs(); //Add this line to configure the Docs Module + + /* Configure customizations for entities from the modules included */ + + builder.Entity(b => + { + b.ConfigureCustomUserProperties(); + }); + + /* Configure your own tables/entities inside the ConfigureQaDoc method */ + + builder.ConfigureMyProject(); + } + } ``` -* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore` as default project. Then write the below command to add the migration for Docs Module. +* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore.DbMigrations` as default project. Then write the below command to add the migration for Docs Module. ```csharp add-migration Added_Docs_Module ``` - When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore\Migrations`. + When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore.DbMigrations\Migrations`. - Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore` is still default project. + Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore.DbMigrations` is still default project. ```csharp update-database @@ -220,7 +228,6 @@ If you choose Entity Framework as your database provider, you need to configure Finally, you can check your database to see the newly created tables. For example you can see `DocsProjects` table must be added to your database. - ### 5- Linking Docs Module The default route for Docs module is; @@ -250,7 +257,7 @@ To add Docs module link to your application menu; } ``` -The `Menu:Docs` keyword is a localization key. To localize the menu text, open `Localization\MyProject\en.json` in the project `Acme.MyProject.Domain`. And add the below line +The `Menu:Docs` keyword is a localization key. To localize the menu text, open `Localization\MyProject\en.json` in the project `Acme.MyProject.Domain`. And add the below line ```json "Menu:Docs": "Documents" @@ -270,7 +277,7 @@ Final look of **en.json** } ``` -The new menu item for Docs Module is added to the menu. Run your web application and browse to `http://localhost:YOUR_PORT_NUMBER/documents` URL. +The new menu item for Docs Module is added to the menu. Run your web application and browse to `http://localhost:YOUR_PORT_NUMBER/documents` URL. You will see a warning says; @@ -316,23 +323,25 @@ You can use [ABP Framework](https://github.com/abpframework/abp/) GitHub documen - ExtraProperties: ```json - {"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***"} + {"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""} ``` Note that `GitHubAccessToken` is masked with `***`. It's a private token that you must get it from GitHub. See https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ -- MainWebsiteUrl: `/` +- MainWebsiteUrl: `/` - LatestVersionBranchName: `master` For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table: ```mssql -INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***"}', N'/', N'master', N'') +INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""}', N'/', N'master', N'') ``` Be aware that `GitHubAccessToken` is masked. It's a private token and you must get your own token and replace the `***` string. +Now you can run the application and navigate to `/Documents`. + #### Sample Project Record for "FileSystem" You can use [ABP Framework](https://github.com/abpframework/abp/) GitHub documents to configure your GitHub document store. @@ -561,11 +570,34 @@ The upper sample `JSON` file renders the below navigation menu as `HTML`. ![Navigation menu](../images/docs-module_download-sample-navigation-menu.png) +Finally a new Docs Module is added to your project which is feeded with GitHub. -Finally a new Docs Module is added to your project which is feeded with GitHub. +## Full-Text Search(Elastic Search) -## Next +The Docs module supports full-text search using Elastic Search. It is not enabled by default. You can configure `DocsElasticSearchOptions` to enable it. + +``` +Configure(options => +{ + options.Enable = true; + options.IndexName = "your_index_name"; //default IndexName is abp_documents +}); +``` + +The `Index` is automatically created after the application starts if the `Index` does not exist. -Docs Module is also available as a standalone application. Check out [VoloDocs](../Apps/VoloDocs). +`DefaultElasticClientProvider` is responsible for creating `IElasticClient`. By default, it reads Elastic Search's `Url` from `IConfiguration`. +If your `IElasticClient` needs additional configuration, please use override `IElasticClientProvider` service and replace it in the [dependency injection](Dependency-Injection.md) system. + +``` +{ + "ElasticSearch": { + "Url": "http://localhost:9200" + } +} +``` + +## Next +Docs Module is also available as a standalone application. Check out [VoloDocs](../Apps/VoloDocs). \ No newline at end of file diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 4ee77d065c..1e1e4e9f81 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -302,7 +302,7 @@ TODO:... Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box: -* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be stay as the first contributor for security**. +* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**. * **QueryStringTenantResolver**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default. * **RouteTenantResolver**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route. * **HeaderTenantResolver**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default. @@ -343,8 +343,10 @@ namespace MyCompany.MyProject { Configure(options => { - //Subdomain format: {0}.mydomain.com (adding as the highest priority resolver) - options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com")); + //Subdomain format: {0}.mydomain.com + //Adding as the second highest priority resolver after 'CurrentUserTenantResolveContributor' to + //ensure the user cannot impersonate a different tenant. + options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com")); }); //... @@ -355,7 +357,7 @@ namespace MyCompany.MyProject {0} is the the placeholder to determine current tenant's unique name. -Instead of ``options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));`` you can use this shortcut: +Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));`` you can use this shortcut: ````C# options.AddDomainTenantResolver("{0}.mydomain.com"); diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md new file mode 100644 index 0000000000..72c8e6c6fd --- /dev/null +++ b/docs/en/UI/Angular/Config-State.md @@ -0,0 +1,290 @@ +# Config State + +`ConfigStateService` is a singleton service, i.e. provided in root level of your application, and is actually a façade for interacting with application configuration state in the `Store`. + +## Before Use + +In order to use the `ConfigStateService` you must inject it in your class as a dependency. + +```js +import { ConfigStateService } from '@abp/ng.core'; + +@Component({ + /* class metadata here */ +}) +class DemoComponent { + constructor(private config: ConfigStateService) {} +} +``` + +You do not have to provide the `ConfigStateService` at module or component/directive level, because it is already **provided in root**. + +## Selector Methods + +`ConfigStateService` has numerous selector methods which allow you to get a specific configuration or all configurations from the `Store`. + +### How to Get All Configurations From the Store + +You can use the `getAll` method of `ConfigStateService` to get all of the configuration object from the store. It is used as follows: + +```js +// this.config is instance of ConfigStateService + +const config = this.config.getAll(); +``` + +### How to Get a Specific Configuration From the Store + +You can use the `getOne` method of `ConfigStateService` to get a specific configuration property from the store. For that, the property name should be passed to the method as parameter. + +```js +// this.config is instance of ConfigStateService + +const currentUser = this.config.getOne("currentUser"); +``` + +On occasion, you will probably want to be more specific than getting just the current user. For example, here is how you can get the `tenantId`: + +```js +const tenantId = this.config.getDeep("currentUser.tenantId"); +``` + +or by giving an array of keys as parameter: + +```js +const tenantId = this.config.getDeep(["currentUser", "tenantId"]); +``` + +FYI, `getDeep` is able to do everything `getOne` does. Just keep in mind that `getOne` is slightly faster. + +#### Config State Properties + +Please refer to `Config.State` type for all the properties you can get with `getOne` and `getDeep`. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L7). + +### How to Get the Application Information From the Store + +The `getApplicationInfo` method is used to get the application information from the environment variables stored as the config state. This is how you can use it: + +```js +// this.config is instance of ConfigStateService + +const appInfo = this.config.getApplicationInfo(); +``` + +This method never returns `undefined` or `null` and returns an empty object literal (`{}`) instead. In other words, you will never get an error when referring to the properties of `appInfo` above. + +#### Application Information Properties + +Please refer to `Config.Application` type for all the properties you can get with `getApplicationInfo`. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L21). + +### How to Get API URL From the Store + +The `getApplicationInfo` method is used to get a specific API URL from the environment variables stored as the config state. This is how you can use it: + +```js +// this.config is instance of ConfigStateService + +const apiUrl = this.config.getApiUrl(); +// environment.apis.default.url + +const searchUrl = this.config.getApiUrl("search"); +// environment.apis.search.url +``` + +This method returns the `url` of a specific API based on the key given as its only parameter. If there is no key, `'default'` is used. + +### How to Get All Settings From the Store + +You can use the `getSettings` method of `ConfigStateService` to get all of the settings object from the configuration state. Here is how you get all settings: + +```js +// this.config is instance of ConfigStateService + +const settings = this.config.getSettings(); +``` + +In addition, the method lets you search settings by **passing a keyword** to it. + +```js +const localizationSettings = this.config.getSettings("Localization"); +/* +{ + 'Abp.Localization.DefaultLanguage': 'en' +} +*/ +``` + +Beware though, **settings search is case sensitive**. + +### How to Get a Specific Setting From the Store + +You can use the `getSetting` method of `ConfigStateService` to get a specific setting from the configuration state. Here is an example: + +```js +// this.config is instance of ConfigStateService + +const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage"); +// 'en' +``` + +### How to Get a Specific Permission From the Store + +You can use the `getGrantedPolicy` method of `ConfigStateService` to get a specific permission from the configuration state. For that, you should pass a policy key as parameter to the method. + +```js +// this.config is instance of ConfigStateService + +const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity"); +// true +``` + +You may also **combine policy keys** to fine tune your selection: + +```js +// this.config is instance of ConfigStateService + +const hasIdentityAndAccountPermission = this.config.getGrantedPolicy( + "Abp.Identity && Abp.Account" +); +// false + +const hasIdentityOrAccountPermission = this.config.getGrantedPolicy( + "Abp.Identity || Abp.Account" +); +// true +``` + +Please consider the following **rules** when creating your permission selectors: + +- Maximum 2 keys can be combined. +- `&&` operator looks for both keys. +- `||` operator looks for either key. +- Empty string `''` as key will return `true` +- Using an operator without a second key will return `false` + +### How to Get Translations From the Store + +The `getLocalization` method of `ConfigStateService` is used for translations. Here are some examples: + +```js +// this.config is instance of ConfigStateService + +const identity = this.config.getLocalization("AbpIdentity::Identity"); +// 'identity' + +const notFound = this.config.getLocalization("AbpIdentity::IDENTITY"); +// 'AbpIdentity::IDENTITY' + +const defaultValue = this.config.getLocalization({ + key: "AbpIdentity::IDENTITY", + defaultValue: "IDENTITY" +}); +// 'IDENTITY' +``` + +Please check out the [localization documentation](./Localization.md) for details. + +## Dispatch Methods + +`ConfigStateService` has several dispatch methods which allow you to conveniently dispatch predefined actions to the `Store`. + +### How to Get Application Configuration From Server + +The `dispatchGetAppConfiguration` triggers a request to an endpoint that responds with the application state and then places this response to the `Store` as configuration state. + +```js +// this.config is instance of ConfigStateService + +this.config.dispatchGetAppConfiguration(); +// returns a state stream which emits after dispatch action is complete +``` + +Note that **you do not have to call this method at application initiation**, because the application configuration is already being received from the server at start. + +### How to Patch Route Configuration + +The `dispatchPatchRouteByName` finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter. + +```js +// this.config is instance of ConfigStateService + +const newRouteConfig: Partial = { + name: "Home", + path: "home", + children: [ + { + name: "Dashboard", + path: "dashboard" + } + ] +}; + +this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig); +// returns a state stream which emits after dispatch action is complete +``` + +### How to Add a New Route Configuration + +The `dispatchAddRoute` adds a new route to the configuration state in the `Store`. For this, the route config should be passed as the parameter of the method. + +```js +// this.config is instance of ConfigStateService + +const newRoute: ABP.Route = { + name: "My New Page", + iconClass: "fa fa-dashboard", + path: "page", + invisible: false, + order: 2, + requiredPolicy: "MyProjectName::MyNewPage" +}; + +this.config.dispatchAddRoute(newRoute); +// returns a state stream which emits after dispatch action is complete +``` + +The `newRoute` will be placed as at root level, i.e. without any parent routes and its url will be stored as `'/path'`. + +If you want **to add a child route, you can do this:** + +```js +// this.config is instance of ConfigStateService + +const newRoute: ABP.Route = { + parentName: "AbpAccount::Login", + name: "My New Page", + iconClass: "fa fa-dashboard", + path: "page", + invisible: false, + order: 2, + requiredPolicy: "MyProjectName::MyNewPage" +}; + +this.config.dispatchAddRoute(newRoute); +// returns a state stream which emits after dispatch action is complete +``` + +The `newRoute` will then be placed as a child of the parent route named `'AbpAccount::Login'` and its url will be set as `'/account/login/page'`. + +#### Route Configuration Properties + +Please refer to `ABP.Route` type for all the properties you can pass to `dispatchSetEnvironment` in its parameter. It can be found in the [common.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L27). + +### How to Set the Environment + +The `dispatchSetEnvironment` places environment variables passed to it in the `Store` under the configuration state. Here is how it is used: + +```js +// this.config is instance of ConfigStateService + +this.config.dispatchSetEnvironment({ + /* environment properties here */ +}); +// returns a state stream which emits after dispatch action is complete +``` + +Note that **you do not have to call this method at application initiation**, because the environment variables are already being stored at start. + +#### Environment Properties + +Please refer to `Config.Environment` type for all the properties you can pass to `dispatchSetEnvironment` as parameter. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13). diff --git a/docs/en/UI/AspNetCore/Bundling-Minification.md b/docs/en/UI/AspNetCore/Bundling-Minification.md index fc4ddaced3..5af3f177d5 100644 --- a/docs/en/UI/AspNetCore/Bundling-Minification.md +++ b/docs/en/UI/AspNetCore/Bundling-Minification.md @@ -51,7 +51,7 @@ This bundle defines a style bundle with a **unique name**: `MyGlobalBundle`. It' * ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests. * ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...). -* The bundle files may be **physical** files or [**virtual/embedded** files](../Virtual-File-System.md). +* The bundle files may be **physical** files or [**virtual/embedded** files](../../Virtual-File-System.md). * ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment). ### Importing The Bundling Tag Helpers @@ -102,7 +102,7 @@ The bundle name will be *scripts.my-scripts* for the example above ("/" is repla ## Bundling Options -If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class. +If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../../Module-Development-Basics.md) class. ### Creating A New Bundle @@ -143,7 +143,7 @@ This time, no file defined in the tag helper definition because the bundle files ### Configuring An Existing Bundle -ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a depended module. Example: +ABP supports [modularity](../../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a depended module. Example: ````C# [DependsOn(typeof(MyWebModule))] @@ -159,7 +159,7 @@ public class MyWebExtensionModule : AbpModule bundle.AddFiles( "/scripts/my-extension-script.js" ); - }); + }); }); } } @@ -195,7 +195,7 @@ services.Configure(options => .ScriptBundles .Configure("MyGlobalBundle", bundle => { bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor)); - }); + }); }); ```` @@ -327,7 +327,7 @@ services.Configure(options => .AddFiles( "/styles/mytheme-global-styles.css" ); - }); + }); }); ```` diff --git a/docs/en/UI/AspNetCore/Customization-User-Interface.md b/docs/en/UI/AspNetCore/Customization-User-Interface.md index 7e49efd602..c5c974e5eb 100644 --- a/docs/en/UI/AspNetCore/Customization-User-Interface.md +++ b/docs/en/UI/AspNetCore/Customization-User-Interface.md @@ -9,7 +9,7 @@ This section covers the [Razor Pages](https://docs.microsoft.com/en-us/aspnet/co You typically have three kind of override requirement for a page: * Overriding **only the Page Model** (C#) side to perform additional logic without changing the page UI. -* Overring **only the Razor Page** (.chtml file) to change the UI without changing the c# behind the page. +* Overriding **only the Razor Page** (.chtml file) to change the UI without changing the c# behind the page. * **Completely overriding** the page. ### Overriding a Page Model (C#) @@ -158,7 +158,7 @@ If you need, you can also replace [the code behind c# class](https://github.com/ Just as explained above, you can replace any component, layout or c# class of the used theme. See the [theming document](Theming.md) for more information on the theming system. -### Overriding Static Resources +## Overriding Static Resources Overriding a static embedded resource (like JavaScript, Css or image files) of a module is pretty easy. Just place a file in the same path in your solution and let the Virtual File System to handle it. @@ -437,4 +437,34 @@ See the layouts section below to learn more about the layout system. ### Layouts -TODO \ No newline at end of file +Layout system allows themes to define standard, named layouts and allows any page to select a proper layout for its purpose. There are three pre-defined layouts: + +* "**Application**": The main (and the default) layout for an application. It typically contains header, menu (sidebar), footer, toolbar... etc. +* "**Account**": This layout is used by login, register and other similar pages. It is used for the pages under the `/Pages/Account` folder by default. +* "**Empty**": Empty and minimal layout. + +These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are standard layout names and implemented by all the themes out of the box. + +#### Layout Location + +You can find the layout files [here](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary. + +#### ITheme + +ABP Framework uses the `ITheme` service to get the layout location by the layout name. You can replace this service to dynamically select the layout location. + +#### IThemeManager + +`IThemeManager` is used to obtain the current theme and get the layout path. Any page can determine the layout of its own. Example: + +````html +@using Volo.Abp.AspNetCore.Mvc.UI.Theming +@inject IThemeManager ThemeManager +@{ + Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty); +} +```` + +This page will use the empty layout. You use `ThemeManager.CurrentTheme.GetEmptyLayout();` extension method as a shortcut. + +If you want to set the layout for all the pages under a specific folder, then write the code above in a `_ViewStart.cshtml` file under that folder. diff --git a/docs/en/UI/AspNetCore/Widgets.md b/docs/en/UI/AspNetCore/Widgets.md index a45056ba68..062fefb078 100644 --- a/docs/en/UI/AspNetCore/Widgets.md +++ b/docs/en/UI/AspNetCore/Widgets.md @@ -4,8 +4,8 @@ ABP provides a model and infrastructure to create **reusable widgets**. Widget s * Have **scripts & styles** dependencies for your widget. * Create **dashboards** with widgets used inside. -* Define widgets in reusable **[modules](../Module-Development-Basics.md)**. -* Co-operate widgets with **[authorization](../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems. +* Define widgets in reusable **[modules](../../Module-Development-Basics.md)**. +* Co-operate widgets with **[authorization](../../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems. ## Basic Widget Definition @@ -13,7 +13,7 @@ ABP provides a model and infrastructure to create **reusable widgets**. Widget s As the first step, create a new regular ASP.NET Core View Component: -![widget-basic-files](../images/widget-basic-files.png) +![widget-basic-files](../../images/widget-basic-files.png) **MySimpleWidgetViewComponent.cs**: @@ -184,7 +184,7 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget } ```` -See [the localization document](../Localization.md) to learn about localization resources and keys. +See [the localization document](../../Localization.md) to learn about localization resources and keys. ## Style & Script Dependencies @@ -220,7 +220,7 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget } ```` -ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../Virtual-File-System.md). +ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../../Virtual-File-System.md). ### Defining Bundle Contributors @@ -454,7 +454,7 @@ Used to refresh the widget when needed. It has a filter argument that can be use Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute: * `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application). -* `RequiredPolicies` (`List`): A list of policy names to authorize the user. See [the authorization document](../Authorization.md) for more info about policies. +* `RequiredPolicies` (`List`): A list of policy names to authorize the user. See [the authorization document](../../Authorization.md) for more info about policies. Example: @@ -487,7 +487,7 @@ Configure(options => }); ``` -Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `AbpWidgetOptions`. Example configuration that adds a style for the widget: +Write this into the `ConfigureServices` method of your [module](../../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `AbpWidgetOptions`. Example configuration that adds a style for the widget: ````csharp Configure(options => diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 1491a1bcb9..c273e59346 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -316,6 +316,10 @@ "text": "Permission Management", "path": "UI/Angular/Permission-Management.md" }, + { + "text": "Config State", + "path": "UI/Angular/Config-State.md" + }, { "text": "Component Replacement", "path": "UI/Angular/Component-Replacement.md" diff --git a/docs/en/images/docs-module_download-new-abp-project.png b/docs/en/images/docs-module_download-new-abp-project.png index 0da3b7a67a..6424ad7622 100644 Binary files a/docs/en/images/docs-module_download-new-abp-project.png and b/docs/en/images/docs-module_download-new-abp-project.png differ diff --git a/docs/en/images/docs-module_solution-explorer.png b/docs/en/images/docs-module_solution-explorer.png index cafc38f0b0..2988ec4134 100644 Binary files a/docs/en/images/docs-module_solution-explorer.png and b/docs/en/images/docs-module_solution-explorer.png differ diff --git a/docs/zh-Hans/Modules/Docs.md b/docs/zh-Hans/Modules/Docs.md index f2a92334fb..20382d24f2 100644 --- a/docs/zh-Hans/Modules/Docs.md +++ b/docs/zh-Hans/Modules/Docs.md @@ -26,15 +26,19 @@ ABP框架的[文档](docs.abp.io)也是使用的此模块. 如果你没有现有的ABP项目, 这个步骤向你展示如何在[abp.io](https://abp.io)创建一个新项目并添加文档模块. 如果你本地已经有了一个ABP项目, 那么你可以跳过这一步. -打开 https://abp.io/Templates. 输入项目名称为 `Acme.MyProject`, 选择 `ASP.NET Core Mvc Application` 和选择 `Entity Framework Core` 做为数据库提供者. +推荐使用ABP CLI创建新项目,使用以下命令行: -请注意,本文档包含了 `Entity Framework Core` 提供者 不过你也可以选择 `MongoDB` 做为数据库提供者. +`abp new Acme.MyProject` + +你也可以在浏览器中导航到 https://abp.io/get-started. 输入项目名称为 `Acme.MyProject`, 其它保持默认选项. + +请注意,本文档包含了 `Entity Framework Core` 提供者 不过你也可以选择 `MongoDB` 做为数据库提供者. ![创建新项目](../images/docs-module_download-new-abp-project.png) ### 2- 运行这个空项目 -下载项目后, 解压压缩文档并且打开 `Acme.MyProject.sln`. 你可以看到这个解决方案包含了 `Application`, `Domain`, `EntityFrameworkCore` 和 `Web` 项目. 右键选择 `Acme.MyProject.Web` 项目**设置为启动项目**. +下载项目后, 解压压缩文档并且打开 `Acme.MyProject.sln`. 你可以看到这个解决方案包含了 `Application`, `Application.Contrawcts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `EntityFrameworkCore.DbMigations`, `HttpApi`, `HttpApi.Client` 和 `Web` 项目. 右键选择 `Acme.MyProject.Web` 项目**设置为启动项目**. ![创建新项目](../images/docs-module_solution-explorer.png) @@ -43,12 +47,12 @@ ABP框架的[文档](docs.abp.io)也是使用的此模块. ```json { "ConnectionStrings": { - "Default": "Server=localhost;Database=MyProject;Trusted_Connection=True;MultipleActiveResultSets=true" + "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProject;Trusted_Connection=True;MultipleActiveResultSets=true" } } ``` -打开Visual Studio包管理控制台选择`src\Acme.MyProject.EntityFrameworkCore` 做为默认项目. 运行 `Update-Database` 命令创建数据库. 数据库`MyProject`将在数据库服务器中创建. +运行 `Acme.MyProject.DbMigrator` 项目,它会负责应用迁移与初始化种子数据. 数据库`MyProject`将在数据库服务器中创建. 现在一个空的ABP项目已经创建完成! 现在你可以运行项目并且查看网站. @@ -56,39 +60,28 @@ ABP框架的[文档](docs.abp.io)也是使用的此模块. ### 2- 引用文档模块包 -文档模块包托管在Nuget上面. 需要有四个包安装到你的应用程序中. 每个包必须安装到相关的项目. - -* [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) 需要安装到 `Acme.MyProject.Domain` 项目. +文档模块包托管在Nuget上面. 需要有四个包安装到你的应用程序中. 每个包必须安装到相关的项目. - * 修改 `Acme.MyProject.Domain.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. +建议使用ABP CLI安装模块,在解决方案文件 (`.sln`) 目录打开 `CMD` 窗口,运行以下命令: - ```csharp - - ``` +`abp add-module Volo.Docs` -* [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) 需要安装到 `Acme.MyProject.EntityFrameworkCore` 项目. +或者你也可以手动安装nuget包到每个项目: - * 修改 `Acme.MyProject.EntityFrameworkCore.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. +* 安装[Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget包到 `Acme.MyProject.Domain` 项目. - ```csharp - - ``` + `Install-Package Volo.Docs.Domain` -* [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) 需要安装到 `Acme.MyProject.Application` 项目. +* 安装[Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) nuget包到 `Acme.MyProject.EntityFrameworkCore` 项目. - * 修改 `Acme.MyProject.Application.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. + `Install-Package Volo.Docs.EntityFrameworkCore` - ```csharp - - ``` +* 安装[Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) nuget包到 `Acme.MyProject.Application` 项目. -* [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Web/) 需要安装到 `Acme.MyProject.Web` 项目. + `Install-Package Volo.Docs.Application` - * 修改 `Acme.MyProject.Web.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. - - ```csharp - - ``` +* 安装[Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget包到 `Acme.MyProject.Web` 项目. + `Install-Package Volo.Docs.Web` ### 3- 添加模块依赖 @@ -176,37 +169,58 @@ ABP框架的[文档](docs.abp.io)也是使用的此模块. #### 4.1- Entity Framework 集成 -如果你选择了Entity Framework 做为数据库供应者,你需要在DbContext中配置文档模块. 做以下操作; +如果你选择了Entity Framework 做为数据库供应者,你需要配置文档模块. 做以下操作; -* 打开 `MyProjectDbContext.cs` 并且添加 `modelBuilder.ConfigureDocs()` 到 `OnModelCreating()` 方法中 +* 打开 `MyProjectMigrationsDbContext.cs` 并且添加 `builder.ConfigureDocs()` 到 `OnModelCreating()` 方法中 ```csharp - [ConnectionStringName("Default")] - public class MyProjectDbContext : AbpDbContext - { - public MyProjectDbContext(DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - //... - modelBuilder.ConfigureDocs(); - } - } + public class MyProjectMigrationsDbContext : AbpDbContext + { + public MyProjectMigrationsDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureIdentity(); + builder.ConfigureIdentityServer(); + builder.ConfigureFeatureManagement(); + builder.ConfigureTenantManagement(); + builder.ConfigureDocs(); //Add this line to configure the Docs Module + + /* Configure customizations for entities from the modules included */ + + builder.Entity(b => + { + b.ConfigureCustomUserProperties(); + }); + + /* Configure your own tables/entities inside the ConfigureQaDoc method */ + + builder.ConfigureMyProject(); + } + } ``` -* 打开 `Visual Studio` 的 `包管理控制台` 选择 `Acme.MyProject.EntityFrameworkCore` 做为默认项目. 然后编写以下命令为文档模块添加迁移. +* 打开 `Visual Studio` 的 `包管理控制台` 选择 `Acme.MyProject.EntityFrameworkCore.DbMigrations` 做为默认项目. 然后编写以下命令为文档模块添加迁移. ```csharp add-migration Added_Docs_Module ``` - 当命令执行成功后 , 你会看到`Acme.MyProject.EntityFrameworkCore\Migrations` 目录下有名为 `20181221111621_Added_Docs_Module` 的迁移文件. + 当命令执行成功后 , 你会看到`Acme.MyProject.EntityFrameworkCore.DbMigrations\Migrations` 目录下有名为 `20181221111621_Added_Docs_Module` 的迁移文件. - 现在更新数据库. 在 `Visual Studio` 的 `包管理控制台` 中执行以下代码. 要确认已 `Acme.MyProject.EntityFrameworkCore` 项目设置为默认项目. + 现在更新数据库. 在 `Visual Studio` 的 `包管理控制台` 中执行以下代码. 要确认已 `Acme.MyProject.EntityFrameworkCore.DbMigrations` 项目设置为默认项目. ```csharp update-database @@ -309,7 +323,7 @@ There are no projects yet! - ExtraProperties: ```json - {"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs/zh-Hans/","GitHubAccessToken":"***"} + {"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs/zh-Hans/","GitHubAccessToken":"***","GitHubUserAgent":""} ``` 注意 `GitHubAccessToken` 用 `***` 掩盖. 这是一个私人令牌,你必须从GitHub获取它. 请参阅 https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ @@ -321,11 +335,13 @@ There are no projects yet! 对于 `SQL` 数据库,你可以使用下面的 `T-SQL` 命令将指定的示例插入到 `DocsProjects` 表中: ```mssql -INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***"}', N'/', N'master', N'') +INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""}', N'/', N'master', N'') ``` 请注意,`GitHubAccessToken` 被屏蔽了.它是一个私人令牌,你必须获得自己的令牌并替换 `***` 字符串. +现在你可以运行应用程序并导航到 `/Documents`. + #### "FileSystem" 项目的示例记录 你可以使用 [ABP Framework](https://github.com/abpframework/abp/) GitHub文档来配置你的文件系统存储. @@ -556,6 +572,30 @@ This document assumes that you prefer to use **{{ UI_Value }}** as the UI framew 最后,为您的项目添加了一个新的Docs模块, 该模块由GitHub提供. +## 全文搜索(Elastic Search) + +文档模块支持使用Elastic Search对内容进行全文搜索. 默认没有启用, 你可以配置`DocsElasticSearchOptions`启用它. + +``` +Configure(options => +{ + options.Enable = true; + options.IndexName = "your_index_name"; //default IndexName is abp_documents +}); +``` + +应用程序启动后如果`Index`不存在则会自动创建`Index`. + +`DefaultElasticClientProvider`负责创建`IElasticClient`, 默认情况下它会从`IConfiguration`中读取Elastic Search的`Url`. +如果你的IElasticClient需要其它配置请使用重写IElasticClientProvider服务并在依赖注入系统中替换它. +``` +{ + "ElasticSearch": { + "Url": "http://localhost:9200" + } +} +``` + ## 下一步 文档模块也可以做为独立的应用程序. 查看 [VoloDocs](../Apps/VoloDocs). \ No newline at end of file diff --git a/docs/zh-Hans/Multi-Tenancy.md b/docs/zh-Hans/Multi-Tenancy.md index 1cfa2996d2..8913c27ede 100644 --- a/docs/zh-Hans/Multi-Tenancy.md +++ b/docs/zh-Hans/Multi-Tenancy.md @@ -343,8 +343,8 @@ namespace MyCompany.MyProject { Configure(options => { - //子域名格式: {0}.mydomain.com (作为最高优先级解析器添加) - options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com")); + //子域名格式: {0}.mydomain.com (作为第二优先级解析器添加, 位于CurrentUserTenantResolveContributor之后) + options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com")); }); //... @@ -355,7 +355,7 @@ namespace MyCompany.MyProject {0}是用来确定当前租户唯一名称的占位符. -你可以使用下面的方法,代替``options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));``: +你可以使用下面的方法,代替``options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));``: ````C# options.AddDomainTenantResolver("{0}.mydomain.com"); diff --git a/docs/zh-Hans/Samples/Microservice-Demo.md b/docs/zh-Hans/Samples/Microservice-Demo.md index a96571c881..9b559797f7 100644 --- a/docs/zh-Hans/Samples/Microservice-Demo.md +++ b/docs/zh-Hans/Samples/Microservice-Demo.md @@ -24,82 +24,15 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 下图展示了该系统: -![microservice-sample-diagram](../images/microservice-sample-diagram-2.png) +![microservice-sample-diagram](../images/microservice-sample-diagram-3.png) ### 源码 你可以从[GitHub仓库](https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo)获取源码. -### 状态 - -该示例的初始版本已完成.其他改进仍在开发中. - ## 运行解决方案 -您可以从 **源代码** 或者预先配置好的 **docker-compose** 文件运行. - -### 使用Docker容器 - -#### 预先要求 - -由于所有依赖项都已预先配置, 因此作为Docker容器运行更容易. 你只需要安装[最新的docker](https://docs.docker.com/compose/install/). - -#### 运行容器 - -- 克隆或下载 [ABP仓库](https://github.com/abpframework/abp). - -- 在存储库的`samples/MicroserviceDemo`文件夹中打开命令行. - -- 从Docker Hub中拉取image: - - ``` - docker-compose -f docker-compose.yml -f docker-compose.migrations.yml pull - ``` - -- 如果要在本地构建映像, 可以跳过上述步骤, 使用build命令: - - ``` - docker-compose -f docker-compose.yml -f docker-compose.migrations.yml build - ``` - - 根据你的电脑配置, 构建image可能需要**很长时间**. - -- 还原 SQL Server 数据库: - - ``` - docker-compose -f docker-compose.yml -f docker-compose.migrations.yml run restore-database - ``` - -- 启动容器: - - ``` - docker-compose up -d - ``` - -- 将此行添加到`hosts`文件的末尾: - - ``` - 127.0.0.1 auth-server - ``` - - hosts文件位于Windows上的`C:\Windows\System32\Drivers\etc\hosts`文件夹, Linux/MacOS的`/etc/hosts`中. - -#### 运行应用程序 - -你可能想要了解容器中运行一些应用程序: - -* 后端管理应用程序 (BackendAdminApp.Host): `http://localhost:51512` - *(用于管理系统中的用户和产品)* -* 公共网站 (PublicWebsite.Host): `http://localhost:51513` - *(用于列出产品并运行/管理博客模块)* -* 认证服务器 (AuthServer.Host): `http://auth-server:51511/` - *(用作使用IdentityServer4构建的单点登录和身份验证服务器)* -* Kibana UI: `http://localhost:51510` - *(用于显示/跟踪所有服务/应用程序/网关写入的日志)* - -### 从源代码运行 - -#### 预先要求 +### 预先要求 为了能够从源代码运行解决方案, 应在你的计算机上安装并运行以下工具: @@ -110,19 +43,19 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 * [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+ * [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (可选,建议显示日志) -#### 打开并构建Visual Studio解决方案 +### 打开并构建Visual Studio解决方案 * 在Visual Studio 2017 (15.9.0+)中打开`samples\MicroserviceDemo\MicroserviceDemo.sln`. * 在`samples\MicroserviceDemo`文件夹中的命令行运行`dotnet restore`命令. * 在Visual Studio中构建解决方案. -#### 创建数据库 +### 创建数据库 MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库的结构。其实你可以很轻松的创建数据库,因为这个解决方案配置了使用 Entity Core Code First 来做迁移。 这个解决方案中有两个 SQL server 数据库。 -##### MsDemo_Identity 数据库 +#### MsDemo_Identity 数据库 * 右键 `AuthServer.Host` 项目,然后点击 `设置为启动项目`. * 打开 **程序包管理器控制台** (工具 -> NuGet 包管理器 -> 程序包管理器控制台) @@ -131,7 +64,7 @@ MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库 ![microservice-sample-update-database-authserver](../images/microservice-sample-update-database-authserver.png) -##### MsDemo_ProductManagement +#### MsDemo_ProductManagement * 右键 `ProductService.Host` 项目,然后点击 `设置为启动项目`. * 打开 **程序包管理器控制台** (工具 -> NuGet 包管理器 -> 程序包管理器控制台) @@ -140,12 +73,13 @@ MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库 ![microservice-sample-update-database-products](../images/microservice-sample-update-database-products.png) -#### 运行项目 +### 运行项目 按以下顺序运行项目(右键单击每个项目设置为启动项目,按Ctrl+F5运行,无需调试): * AuthServer.Host * IdentityService.Host +* TenantManagementService.Host * BloggingService.Host * ProductService.Host * InternalGateway.Host @@ -158,7 +92,7 @@ MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库 Visual Studio解决方案由多个项目组成,每个项目在系统中具有不同的角色: -![microservice-sample-solution](../images/microservice-sample-solution.png) +![microservice-sample-solution](../images/microservice-sample-solution-2.png) ### 应用程序(Applications) @@ -182,6 +116,7 @@ Visual Studio解决方案由多个项目组成,每个项目在系统中具有不 微服务没有UI,但暴露了一些REST API. - **IdentityService.Host**: 托管用于管理用户和角色的ABP Identity模块. 它没有其他服务,仅托管Identity模块的API. +- **TenantManagementService.Host**: 托管用于管理角色的ABP租户管理模块. 它没有其他服务,仅托管租户管理模块的API. - **BloggingService.Host**: 托管ABP博客模块,该模块用于管理博客和帖子(典型的博客应用程序). 它没有其他服务,仅托管Blogging模块的API. - **ProductService.Host**: 托管用于管理产品的产品模块(位于解决方案内). 它还包含用于创建/更新产品管理数据库架构的EF Core迁移. @@ -193,7 +128,7 @@ Visual Studio解决方案由多个项目组成,每个项目在系统中具有不 此解决方案使用多个数据库: -* **MsDemo_Identity**: 一个SQL数据库. 默认使用**SQL Server**,但可以是EF Core支持的任何DBMS. 由AuthServer和IdentityService共享. 审计日志,权限和设置也存储在此数据库中(虽然它们可以轻松拥有自己的数据库,共享相同的数据库以保持简单). +* **MsDemo_Identity**: 一个SQL数据库. 默认使用**SQL Server**,但可以是EF Core支持的任何DBMS. 由AuthServer,IdentityService和TenantManagementService共享. 审计日志,权限和设置也存储在此数据库中(虽然它们可以轻松拥有自己的数据库,共享相同的数据库以保持简单). * **MsDemo_ProductManagement**: 一个SQL数据库. 同样默认使用 **SQL Server**,但可以是EF Core支持的任何DBMS. 由ProductService用作专用数据库. * **MsDemo_Blogging**: **MongoDB**数据库. 由BloggingService使用. * **Elasticsearch**: 用于在Serilog上写日志. @@ -1474,3 +1409,7 @@ ABP提供自动审计日志记录,详细保存每个请求(当前用户,浏览 所有服务和应用程序都配置为编写审核日志. 审核日志将保存到MsDemo_Identity SQL数据库中. 因此,您可以从单个点查询所有应用程序的所有审核日志. 审核日志记录具有`CorrelationId`属性,可用于跟踪请求. 当服务在单个Web请求中调用另一个服务时,它们都会使用相同的`CorrelationId`保存审核日志. 请参阅数据库中的`AbpAuditLogs`表. + +### 多租户 + +该解决方案已配置提供[多租户](../Multi-Tenancy.md)系统,其中每个租户可以拥有其隔离的用户,角色,权限和其他数据. \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md b/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md index b7c8c0a1b1..c0f999f996 100644 --- a/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md +++ b/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md @@ -52,7 +52,7 @@ namespace MyCompany.MyProject * 当首次请求时,ABP从提供的文件中 **(延迟)lazy** 创建. 后续将从 **缓存** 中返回内容. 这意味着如果你有条件地将文件添加到包中,它只执行一次, 并且条件的任何更改都不会影响下一个请求的包. * 在`development`环境中ABP会将包文件**单独**添加到页面中, 其他环境(`staging`,`production`...)会自动捆绑和压缩. -* 捆绑文件可以是**物理**文件或[**虚拟/嵌入**](../Virtual-File-System.md)的文件. +* 捆绑文件可以是**物理**文件或[**虚拟/嵌入**](../../Virtual-File-System.md)的文件. * ABP自动将 **版本查询字符串(version query string)** 添加到捆绑文件的URL中,以防止浏览器缓存. 如:?_v=67872834243042(从文件的上次更改日期生成). 即使捆绑文件单独添加到页面(在`development`环境中), 版本控制仍然有效. #### 导入 Bundling Tag Helpers @@ -103,7 +103,7 @@ namespace MyCompany.MyProject ### Bundling 选项 -如果你需要在 **多个页面中使用相同的包** 或想要使用更多 **强大功能**, 你可以在[模块](../Module-Development-Basics.md)类中进行**配置**. +如果你需要在 **多个页面中使用相同的包** 或想要使用更多 **强大功能**, 你可以在[模块](../../Module-Development-Basics.md)类中进行**配置**. #### 创建一个新的捆绑包 @@ -144,7 +144,7 @@ public class MyWebModule : AbpModule #### 配置现有的 Bundle -ABP也支持[模块化](../Module-Development-Basics.md)捆绑. 模块可以修改由依赖模块创建的捆绑包. +ABP也支持[模块化](../../Module-Development-Basics.md)捆绑. 模块可以修改由依赖模块创建的捆绑包. 例如: ````C# diff --git a/docs/zh-Hans/UI/AspNetCore/Layout-Hooks.md b/docs/zh-Hans/UI/AspNetCore/Layout-Hooks.md new file mode 100644 index 0000000000..49b334c6c0 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Layout-Hooks.md @@ -0,0 +1,3 @@ +# Layout Hooks + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Navigation-Menu.md b/docs/zh-Hans/UI/AspNetCore/Navigation-Menu.md new file mode 100644 index 0000000000..4e0164ec72 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Navigation-Menu.md @@ -0,0 +1,3 @@ +# Navigation Menu + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md index 848d043312..76307ad820 100644 --- a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md @@ -1,30 +1,26 @@ # 按钮 -ABP框架定义了Tag Helper用于简单的创建bootstrap按钮. +## 介绍 -`` +`abp-button` 是创建按钮的主要元素. -## 属性 +基本用法: -`` 有7个不同的属性. +````xml +Click Me +```` -* [`button-type`](#button-type) -* [`size`](#size) -* [`busy-text`](#busy-text) -* [`text`](#text) -* [`icon`](#icon) -* [`disabled`](#disabled) -* [`icon-type`](#icon-type) +## Demo -### `button-type` +参阅 [按钮Demo页面](https://bootstrap-taghelpers.abp.io/Components/Buttons) 查看示例. -`button-type` 是一个可选参数. 它的默认值是 `Default`. +## Attributes -`Button` +### `button-type` -你可以为按钮选择以下按钮类型: +指定按钮的主样式/类型. 应为以下值之一: -* `Default` +* `Default` (default value) * `Primary` * `Secondary` * `Success` @@ -45,11 +41,7 @@ ABP框架定义了Tag Helper用于简单的创建bootstrap按钮. ### `size` -`size` 是一个可选参数. 它的默认值是 `Default`. - -`Button` - -你可以为按钮选择以下size类型: +指定按钮的大小. 应为以下值之一: * `Default` * `Small` @@ -62,33 +54,33 @@ ABP框架定义了Tag Helper用于简单的创建bootstrap按钮. ### `busy-text` -`busy-text` 是一个字符串类型参数. 当按钮繁忙时设置该文本. +当按钮busy时显示的文本. ### `text` -`text` 是一个字符串类型参数,显示在按钮上. - -### `icon` +按钮的文本. 如果你只想为为按钮设置文本,这是一种快捷方式. 例: -`icon` 是一个字符串类型参数. 它的值取决于[`icon-type`](#`icon-type`). 默认情况下,我们对图标使用[Font Awesome](https://fontawesome.com/). 要使用它,你需要将 `icon` 参数设置为图标名称. +````xml + +```` -##### 示例 +在这个示例中,你可以使用 self-closing 标签将其缩短. -[fa-address-card](https://fontawesome.com/icons/address-card): ![fa-address-card](fa-address-card.png "Address Card") - -`` - -> 不要忘记: 你不需要写前缀,如果你没有更改 `icon-type` ,它会为[Font Awesome](https://fontawesome.com/)图标自动添加 `fa` 前缀. +### `icon` -### `disabled` +设置按钮的图标. 默认情况下它使用[Font Awesome](https://fontawesome.com/)图标库. 例: -`disabled` 是一个布尔类型参数. 如果你设值为 `true`, 按钮会被禁用. +````xml + +```` ### `icon-type` -`icon-type` 是一个可选参数。它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它. +如果你不想使用font-awesome,你有两个选项: + +1. 设置 `icon-type` 为 `Other`,并为你的按钮编写图标样式. +2. 如果你不使用图标,请手动使用opening和closing标签,并在标签内写任何代码. -你可以为按钮选择以下图标类型: +### `disabled` -* `FontAwesome` -* `Other` +设置为 `true` 禁用按钮. \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md index 1f19d50701..acbb9e872b 100644 --- a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md @@ -1,3 +1,26 @@ -## ABP Tag Helpers +# ABP Tag Helpers -"ABP tag helpers" 文档还在创建中. 你现在可以参阅[组件演示](http://bootstrap-taghelpers.abp.io/). \ No newline at end of file +ABP框架定义了一组**标签助手组件**. 简化开发ASP.NET Core (MVC / Razor Pages) 应用程序界面. + +## bootstrap 组件包装 + +大多数标签助手是[Bootstrap](https://getbootstrap.com/) (v4+)的包装. 编写bootstrap代码并不是那么简单,其中包含太多的重复HTML标签并且也没有类型安全. ABP标签助手使其 **简单** 并且 **类型安全**. + +我们的目标并不是100%的包装bootstrap组件. 仍然可以编写 **原生bootstrap代码** (实际上标签助手生成的也是原生的bootstrap代码), 但我们建议尽量使用标签助手. + +ABP框架还向标准bootstrap组件添加了一些**实用的功能**. + +这里是ABP框架包装的组件列表: + +* [Buttons](Buttons.md) +* ... + +> 在为所有的标签助手完成文档之前,你可以访问 https://bootstrap-taghelpers.abp.io/ 查看在线示例. + +## 表单元素 + +参阅 [demo](https://bootstrap-taghelpers.abp.io/Components/FormElements). + +## 动态表单 + +参阅 [demo](https://bootstrap-taghelpers.abp.io/Components/DynamicForms). \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Theming.md b/docs/zh-Hans/UI/AspNetCore/Theming.md index 470ef1a458..10486dd6c5 100644 --- a/docs/zh-Hans/UI/AspNetCore/Theming.md +++ b/docs/zh-Hans/UI/AspNetCore/Theming.md @@ -1,3 +1,3 @@ -# Theming +# ASP.NET Core MVC / Razor Pages 主题 TODO \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Toolbars.md b/docs/zh-Hans/UI/AspNetCore/Toolbars.md new file mode 100644 index 0000000000..cc3bcd95be --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Toolbars.md @@ -0,0 +1,3 @@ +# Toolbars + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Widgets.md b/docs/zh-Hans/UI/AspNetCore/Widgets.md index 0e75a24b6f..5f86595ba2 100644 --- a/docs/zh-Hans/UI/AspNetCore/Widgets.md +++ b/docs/zh-Hans/UI/AspNetCore/Widgets.md @@ -2,10 +2,10 @@ ABP为创建**可重用的部件**提供了模型和基础设施. 部件系统是[ASP.NET Core ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components)的扩展. 在你有以下需求时,小部件会非常有用; -* 在可复用的 **[模块](../Module-Development-Basics.md)** 中定义部件. +* 在可复用的 **[模块](../../Module-Development-Basics.md)** 中定义部件. * 在部件中引用 **scripts & styles** 脚本. -* 使用部件创建 **[仪表盘](Dashboards.md)**. -* 支持 **[授权](../Authorization.md)** 与 **[捆绑`bundling`](Bundling-Minification.md)** 的部件 +* 使用部件创建 **仪表盘**. +* 支持 **[授权](../../Authorization.md)** 与 **[捆绑`bundling`](Bundling-Minification.md)** 的部件 ## 基本部件定义 @@ -13,7 +13,7 @@ ABP为创建**可重用的部件**提供了模型和基础设施. 部件系统 第一部,创建一个新的ASP.NET Core View Component: -![widget-basic-files](../images/widget-basic-files.png) +![widget-basic-files](../../images/widget-basic-files.png) **MySimpleWidgetViewComponent.cs**: @@ -132,7 +132,7 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget } ```` -参阅 [本地化文档](../Localization.md) 学习关于本地化资源的更多内容. +参阅 [本地化文档](../../Localization.md) 学习关于本地化资源的更多内容. ## 引用 Style & Script @@ -168,7 +168,7 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget } ```` -ABP会考虑到这些依赖关系, 在view/page中使用正确的方法添加部件 . 样式和script可以是物理文件也可以是虚拟文件. 它于[虚拟文件系统](../Virtual-File-System.md)完全集成]. +ABP会考虑到这些依赖关系, 在view/page中使用正确的方法添加部件 . 样式和script可以是物理文件也可以是虚拟文件. 它于[虚拟文件系统](../../Virtual-File-System.md)完全集成]. ### 定义 Bundle @@ -227,7 +227,7 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget 某些组件可能只对通过身份验证或授权的用户可用,这时可以使用 `Widget` attribute 的以下属性: * `RequiresAuthentication` (`bool`): 设置为true,只有通过身份验证的用户(登录用户)可用. -* `RequiredPolicies` (`List`): 授权用户的策略名称列表. 有关策略的详细信息请参阅[授权文档](../Authorization.md). +* `RequiredPolicies` (`List`): 授权用户的策略名称列表. 有关策略的详细信息请参阅[授权文档](../../Authorization.md). 示例: @@ -260,7 +260,7 @@ Configure(options => }); ``` -将上面的代码写到[模块](../Module-Development-Basics.md)的 `ConfigureServices` 方法中. `AbpWidgetOptions` 可以完成 `Widget` attribute 的所有功能. 比如为组件添加样式: +将上面的代码写到[模块](../../Module-Development-Basics.md)的 `ConfigureServices` 方法中. `AbpWidgetOptions` 可以完成 `Widget` attribute 的所有功能. 比如为组件添加样式: ````csharp Configure(options => diff --git a/docs/zh-Hans/images/bookstore-added-brand-files.png b/docs/zh-Hans/images/bookstore-added-brand-files.png new file mode 100644 index 0000000000..15ef283a2a Binary files /dev/null and b/docs/zh-Hans/images/bookstore-added-brand-files.png differ diff --git a/docs/zh-Hans/images/bookstore-added-logo.png b/docs/zh-Hans/images/bookstore-added-logo.png new file mode 100644 index 0000000000..55af77a5f4 Binary files /dev/null and b/docs/zh-Hans/images/bookstore-added-logo.png differ diff --git a/docs/zh-Hans/images/bookstore-added-role-js-file.png b/docs/zh-Hans/images/bookstore-added-role-js-file.png new file mode 100644 index 0000000000..00881bf808 Binary files /dev/null and b/docs/zh-Hans/images/bookstore-added-role-js-file.png differ diff --git a/docs/zh-Hans/images/bookstore-brand-area-highlighted.png b/docs/zh-Hans/images/bookstore-brand-area-highlighted.png new file mode 100644 index 0000000000..7ab1b8fb7f Binary files /dev/null and b/docs/zh-Hans/images/bookstore-brand-area-highlighted.png differ diff --git a/docs/zh-Hans/images/bookstore-global-css-file.png b/docs/zh-Hans/images/bookstore-global-css-file.png new file mode 100644 index 0000000000..e764fef8ee Binary files /dev/null and b/docs/zh-Hans/images/bookstore-global-css-file.png differ diff --git a/docs/zh-Hans/images/bookstore-google-analytics-view-component.png b/docs/zh-Hans/images/bookstore-google-analytics-view-component.png new file mode 100644 index 0000000000..e548a00935 Binary files /dev/null and b/docs/zh-Hans/images/bookstore-google-analytics-view-component.png differ diff --git a/docs/zh-Hans/images/bookstore-menus-highlighted.png b/docs/zh-Hans/images/bookstore-menus-highlighted.png new file mode 100644 index 0000000000..c4f3237b00 Binary files /dev/null and b/docs/zh-Hans/images/bookstore-menus-highlighted.png differ diff --git a/docs/zh-Hans/images/bookstore-notification-icon-on-toolbar.png b/docs/zh-Hans/images/bookstore-notification-icon-on-toolbar.png new file mode 100644 index 0000000000..817491e9eb Binary files /dev/null and b/docs/zh-Hans/images/bookstore-notification-icon-on-toolbar.png differ diff --git a/docs/zh-Hans/images/bookstore-notification-view-component.png b/docs/zh-Hans/images/bookstore-notification-view-component.png new file mode 100644 index 0000000000..3fc300efe1 Binary files /dev/null and b/docs/zh-Hans/images/bookstore-notification-view-component.png differ diff --git a/docs/zh-Hans/images/bookstore-toolbar-highlighted.png b/docs/zh-Hans/images/bookstore-toolbar-highlighted.png new file mode 100644 index 0000000000..f2b7c3f57e Binary files /dev/null and b/docs/zh-Hans/images/bookstore-toolbar-highlighted.png differ diff --git a/docs/zh-Hans/images/docs-module_download-new-abp-project.png b/docs/zh-Hans/images/docs-module_download-new-abp-project.png index 0da3b7a67a..6424ad7622 100644 Binary files a/docs/zh-Hans/images/docs-module_download-new-abp-project.png and b/docs/zh-Hans/images/docs-module_download-new-abp-project.png differ diff --git a/docs/zh-Hans/images/docs-module_solution-explorer.png b/docs/zh-Hans/images/docs-module_solution-explorer.png index cafc38f0b0..2988ec4134 100644 Binary files a/docs/zh-Hans/images/docs-module_solution-explorer.png and b/docs/zh-Hans/images/docs-module_solution-explorer.png differ diff --git a/docs/zh-Hans/images/microservice-sample-diagram-3.png b/docs/zh-Hans/images/microservice-sample-diagram-3.png new file mode 100644 index 0000000000..b8066ef6be Binary files /dev/null and b/docs/zh-Hans/images/microservice-sample-diagram-3.png differ diff --git a/docs/zh-Hans/images/microservice-sample-solution-2.png b/docs/zh-Hans/images/microservice-sample-solution-2.png new file mode 100644 index 0000000000..3da5d160f8 Binary files /dev/null and b/docs/zh-Hans/images/microservice-sample-solution-2.png differ diff --git a/docs/zh-Hans/images/overriding-login-cshtml.png b/docs/zh-Hans/images/overriding-login-cshtml.png new file mode 100644 index 0000000000..d6daa4a141 Binary files /dev/null and b/docs/zh-Hans/images/overriding-login-cshtml.png differ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/BasicTheme.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/BasicTheme.cs index 01d3a35db2..60d1307b38 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/BasicTheme.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/BasicTheme.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic { public const string Name = "Basic"; - public string GetLayout(string name, bool fallbackToDefault = true) + public virtual string GetLayout(string name, bool fallbackToDefault = true) { switch (name) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs index c808250a3e..31768b5e6a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; namespace Microsoft.AspNetCore.Http { @@ -11,5 +12,13 @@ namespace Microsoft.AspNetCore.Http return stream.GetAllBytes(); } } + + public static async Task GetAllBytesAsync(this IFormFile file) + { + using (var stream = file.OpenReadStream()) + { + return await stream.GetAllBytesAsync(); + } + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 06ce49551b..21a675ddc4 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -43,21 +43,6 @@ namespace Volo.Abp.AspNetCore.Mvc public override void ConfigureServices(ServiceConfigurationContext context) { - //Configure Razor - context.Services.Insert(0, - ServiceDescriptor.Singleton>( - new ConfigureOptions(options => - { - options.FileProviders.Add( - new RazorViewEngineVirtualFileProvider( - context.Services.GetSingletonInstance>() - ) - ); - } - ) - ) - ); - Configure(options => { options.IgnoredInterfaces.AddIfNotContains(typeof(IAsyncActionFilter)); @@ -101,7 +86,16 @@ namespace Volo.Abp.AspNetCore.Mvc }; }) .AddViewLocalization(); //TODO: How to configure from the application? Also, consider to move to a UI module since APIs does not care about it. - + + Configure(options => + { + options.FileProviders.Add( + new RazorViewEngineVirtualFileProvider( + context.Services.GetSingletonInstance>() + ) + ); + }); + context.Services.ExecutePreConfiguredActions(mvcBuilder); //TODO: AddViewLocalization by default..? diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs index 1ce71ca3f9..3326c4e65f 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs @@ -1,5 +1,7 @@ +using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -11,14 +13,34 @@ namespace Volo.Abp.BackgroundWorkers public abstract class BackgroundWorkerBase : IBackgroundWorker { //TODO: Add UOW, Localization and other useful properties..? + public IServiceProvider ServiceProvider { get; set; } + protected readonly object ServiceProviderLock = new object(); - public ILogger Logger { protected get; set; } + protected TService LazyGetRequiredService(ref TService reference) + => LazyGetRequiredService(typeof(TService), ref reference); - protected BackgroundWorkerBase() + protected TRef LazyGetRequiredService(Type serviceType, ref TRef reference) { - Logger = NullLogger.Instance; + if (reference == null) + { + lock (ServiceProviderLock) + { + if (reference == null) + { + reference = (TRef)ServiceProvider.GetRequiredService(serviceType); + } + } + } + + return reference; } - + + public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); + private ILoggerFactory _loggerFactory; + + protected ILogger Logger => _lazyLogger.Value; + private Lazy _lazyLogger => new Lazy(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true); + public virtual Task StartAsync(CancellationToken cancellationToken = default) { Logger.LogDebug("Started background worker: " + ToString()); diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs index a33c5f56ec..2b38059ef8 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; -using Volo.Abp.MultiTenancy; -using Volo.Abp.Reflection; namespace Volo.Abp.Domain.Entities { @@ -13,12 +11,24 @@ namespace Volo.Abp.Domain.Entities /// public static class EntityHelper { - public static bool IsEntity([NotNull] Type type) { return typeof(IEntity).IsAssignableFrom(type); } + public static bool IsEntityWithId([NotNull] Type type) + { + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.GetTypeInfo().IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEntity<>)) + { + return true; + } + } + + return false; + } + public static bool HasDefaultId(IEntity entity) { if (EqualityComparer.Default.Equals(entity.Id, default)) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs index 2ac16ec229..3bda51bd2e 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs @@ -18,6 +18,34 @@ namespace Volo.Abp.Domain.Repositories public interface IRepository : IReadOnlyRepository, IBasicRepository where TEntity : class, IEntity { + /// + /// Get a single entity by the given . + /// It returns null if no entity with the given . + /// It throws if there are multiple entities with the given . + /// + /// A condition to find the entity + /// Set true to include all children of this entity + /// A to observe while waiting for the task to complete. + Task FindAsync( + [NotNull] Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + + /// + /// Get a single entity by the given . + /// It throws if there is no entity with the given . + /// It throws if there are multiple entities with the given . + /// + /// A condition to filter entities + /// Set true to include all children of this entity + /// A to observe while waiting for the task to complete. + Task GetAsync( + [NotNull] Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + /// /// Deletes many entities by function. /// Notice that: All entities fits to given predicate are retrieved and deleted. @@ -30,7 +58,11 @@ namespace Volo.Abp.Domain.Repositories /// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database. /// /// A to observe while waiting for the task to complete. - Task DeleteAsync([NotNull] Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default); + Task DeleteAsync( + [NotNull] Expression> predicate, + bool autoSave = false, + CancellationToken cancellationToken = default + ); } public interface IRepository : IRepository, IReadOnlyRepository, IBasicRepository diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs index 3ade403017..bd7e486d29 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs @@ -49,6 +49,26 @@ namespace Volo.Abp.Domain.Repositories protected abstract IQueryable GetQueryable(); + public abstract Task FindAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default); + + public async Task GetAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + var entity = await FindAsync(predicate, includeDetails, cancellationToken); + + if (entity == null) + { + throw new EntityNotFoundException(typeof(TEntity)); + } + + return entity; + } + public abstract Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default); protected virtual TQueryable ApplyDataFilters(TQueryable query) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index 106e00f77a..a48e5d7da5 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -93,6 +93,20 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return DbSet.AsQueryable(); } + public override async Task FindAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return includeDetails + ? await WithDetails() + .Where(predicate) + .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)) + : await DbSet + .Where(predicate) + .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)); + } + public override async Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) { var entities = await GetQueryable() @@ -173,18 +187,6 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore } - public virtual TEntity Get(TKey id, bool includeDetails = true) - { - var entity = Find(id, includeDetails); - - if (entity == null) - { - throw new EntityNotFoundException(typeof(TEntity), id); - } - - return entity; - } - public virtual async Task GetAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default) { var entity = await FindAsync(id, includeDetails, GetCancellationToken(cancellationToken)); @@ -197,13 +199,6 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return entity; } - public virtual TEntity Find(TKey id, bool includeDetails = true) - { - return includeDetails - ? WithDetails().FirstOrDefault(e => e.Id.Equals(id)) - : DbSet.Find(id); - } - public virtual async Task FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default) { return includeDetails diff --git a/framework/src/Volo.Abp.MailKit/Volo.Abp.MailKit.csproj b/framework/src/Volo.Abp.MailKit/Volo.Abp.MailKit.csproj index 8aa654922a..d282ce5269 100644 --- a/framework/src/Volo.Abp.MailKit/Volo.Abp.MailKit.csproj +++ b/framework/src/Volo.Abp.MailKit/Volo.Abp.MailKit.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs index b81aa7ae87..2457c3c453 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs @@ -31,6 +31,14 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb return ApplyDataFilters(Collection.AsQueryable()); } + public override Task FindAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return Task.FromResult(Collection.AsQueryable().Where(predicate).SingleOrDefault()); + } + public override Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) { var entities = Collection.AsQueryable().Where(predicate).ToList(); diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 3333f1d505..5533cb9f74 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -169,6 +169,16 @@ namespace Volo.Abp.Domain.Repositories.MongoDB return GetMongoQueryable(); } + public override async Task FindAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .Where(predicate) + .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)); + } + public virtual IMongoQueryable GetMongoQueryable() { return ApplyDataFilters( diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs index 7e1dd24ae6..067f4b6a01 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs @@ -246,6 +246,11 @@ namespace Volo.Abp.Domain.Repositories throw new NotImplementedException(); } + public override Task FindAsync(Expression> predicate, bool includeDetails = true, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + public override Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs index 6d1be41f31..03e4ed5465 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs @@ -41,7 +41,7 @@ namespace Volo.Abp.TestApp { var istanbul = new City(IstanbulCityId, "Istanbul"); istanbul.Districts.Add(new District(istanbul.Id, "Bakirkoy", 1283999)); - istanbul.Districts.Add(new District(istanbul.Id, "Mecidiyek�y", 2222321)); + istanbul.Districts.Add(new District(istanbul.Id, "Mecidiyekoy", 2222321)); istanbul.Districts.Add(new District(istanbul.Id, "Uskudar", 726172)); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Tokyo")); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs index 07e6211c09..28c94b02fc 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs @@ -28,6 +28,14 @@ namespace Volo.Abp.TestApp.Testing person.Phones.Count.ShouldBe(2); } + [Fact] + public async Task GetAsync_With_Predicate() + { + var person = await PersonRepository.GetAsync(p => p.Name == "Douglas"); + person.Name.ShouldBe("Douglas"); + person.Phones.Count.ShouldBe(2); + } + [Fact] public async Task FindAsync_Should_Return_Null_For_Not_Found_Entity() { @@ -35,6 +43,14 @@ namespace Volo.Abp.TestApp.Testing person.ShouldBeNull(); } + [Fact] + public async Task FindAsync_Should_Return_Null_For_Not_Found_Entity_With_Predicate() + { + var randomName = Guid.NewGuid().ToString(); + var person = await PersonRepository.FindAsync(p => p.Name == randomName); + person.ShouldBeNull(); + } + [Fact] public async Task DeleteAsync() { diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 2ab190b724..a38a51a16c 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -32,4 +32,8 @@ + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs index 0f573634a4..9381a9b2c8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.Identity { public interface IIdentityRoleAppService : IApplicationService { + Task> GetAllListAsync(); + Task> GetListAsync(PagedAndSortedResultRequestDto input); Task CreateAsync(IdentityRoleCreateDto input); diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs index ffc13fdbca..b02319bc24 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs @@ -27,6 +27,13 @@ namespace Volo.Abp.Identity await _roleManager.GetByIdAsync(id)); } + public virtual async Task> GetAllListAsync() + { + var list = await _roleRepository.GetListAsync(); + return new ListResultDto( + ObjectMapper.Map, List>(list)); + } + public virtual async Task> GetListAsync(PagedAndSortedResultRequestDto input) { var list = await _roleRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index 025bb7fd87..d6d6ca3983 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -82,6 +82,7 @@ "DisplayName:Abp.Identity.Lockout.LockoutDuration": "Lockout duration(seconds)", "DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts": "Max failed access attempts", "DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail": "Require confirmed email", + "DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Enable phone number confirmation", "DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Require confirmed phoneNumber", "DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled": "Is username update enabled", "DisplayName:Abp.Identity.User.IsEmailUpdateEnabled": "Is email update enabled", @@ -95,6 +96,7 @@ "Description:Abp.Identity.Lockout.LockoutDuration": "The duration a user is locked out for when a lockout occurs.", "Description:Abp.Identity.Lockout.MaxFailedAccessAttempts": "The number of failed access attempts allowed before a user is locked out, assuming lock out is enabled.", "Description:Abp.Identity.SignIn.RequireConfirmedEmail": "Whether a confirmed email address is required to sign in.", + "Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Whether the phoneNumber can be confirmed by the user.", "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Whether a confirmed telephone number is required to sign in.", "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Whether the username can be updated by the user.", "Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user." diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs index babeb4fb77..9c07f6c55b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs @@ -30,6 +30,7 @@ private const string SignInPrefix = Prefix + ".SignIn"; public const string RequireConfirmedEmail = SignInPrefix + ".RequireConfirmedEmail"; + public const string EnablePhoneNumberConfirmation = SignInPrefix + ".EnablePhoneNumberConfirmation"; public const string RequireConfirmedPhoneNumber = SignInPrefix + ".RequireConfirmedPhoneNumber"; } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs index efae6e467a..92847004ea 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs @@ -72,6 +72,11 @@ namespace Volo.Abp.Identity false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail"), L("Description:Abp.Identity.SignIn.RequireConfirmedEmail"), true), + new SettingDefinition( + IdentitySettingNames.SignIn.EnablePhoneNumberConfirmation, + true.ToString(), L("DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"), + L("Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"), + true), new SettingDefinition( IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber, false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs index 09760109d0..9925d18abb 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Application.Dtos; @@ -19,6 +20,13 @@ namespace Volo.Abp.Identity _roleAppService = roleAppService; } + [HttpGet] + [Route("all")] + public virtual Task> GetAllListAsync() + { + return _roleAppService.GetAllListAsync(); + } + [HttpGet] public virtual Task> GetListAsync(PagedAndSortedResultRequestDto input) { diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs index aad90e73cc..6c1ac1ff72 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs @@ -29,9 +29,9 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users { UserInfo = new UserInfoViewModel(); - var roleDtoList = await _identityRoleAppService.GetListAsync(new PagedAndSortedResultRequestDto()); + var roleDtoList = (await _identityRoleAppService.GetAllListAsync()).Items; - Roles = ObjectMapper.Map, AssignedRoleViewModel[]>(roleDtoList.Items); + Roles = ObjectMapper.Map, AssignedRoleViewModel[]>(roleDtoList); foreach (var role in Roles) { diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs index 6c8d40c14d..98a32fd1bd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs @@ -31,9 +31,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users { UserInfo = ObjectMapper.Map(await _identityUserAppService.GetAsync(id)); - Roles = ObjectMapper.Map, AssignedRoleViewModel[]>( - (await _identityRoleAppService.GetListAsync(new PagedAndSortedResultRequestDto())).Items - ); + Roles = ObjectMapper.Map, AssignedRoleViewModel[]>((await _identityRoleAppService.GetAllListAsync()).Items); var userRoleNames = (await _identityUserAppService.GetRolesAsync(UserInfo.Id)).Items.Select(r => r.Name).ToList(); foreach (var role in Roles) diff --git a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs index 663cc07786..5d64bb6c78 100644 --- a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs @@ -34,6 +34,18 @@ namespace Volo.Abp.Identity result.Id.ShouldBe(moderator.Id); } + [Fact] + public async Task GetAllListAsync() + { + //Act + + var result = await _roleAppService.GetAllListAsync(); + + //Assert + + result.Items.Count.ShouldBeGreaterThan(0); + } + [Fact] public async Task GetListAsync() { diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs index 7561835ee3..189d7d0f80 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs @@ -1,7 +1,22 @@ -namespace Volo.Abp.TenantManagement +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.TenantManagement.Localization; + +namespace Volo.Abp.TenantManagement { public class TenantCreateDto : TenantCreateOrUpdateDtoBase { + [Required] + [EmailAddress] + [MaxLength(256)] + public string AdminEmailAddress { get; set; } + + [Required] + [MaxLength(128)] + public string AdminPassword { get; set; } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs index a5a9b4af3d..b02da79b79 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs @@ -1,7 +1,11 @@ -namespace Volo.Abp.TenantManagement +using System.ComponentModel.DataAnnotations; + +namespace Volo.Abp.TenantManagement { public abstract class TenantCreateOrUpdateDtoBase { + [Required] + [StringLength(TenantConsts.MaxNameLength)] public string Name { get; set; } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs index 3dabc569bd..cbe9b67c76 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs @@ -15,7 +15,7 @@ namespace Volo.Abp.TenantManagement protected ITenantManager TenantManager { get; } public TenantAppService( - ITenantRepository tenantRepository, + ITenantRepository tenantRepository, ITenantManager tenantManager, IDataSeeder dataSeeder) { @@ -51,10 +51,13 @@ namespace Volo.Abp.TenantManagement { //TODO: Handle database creation? - //TODO: Set admin email & password..? - await DataSeeder.SeedAsync(tenant.Id); + await DataSeeder.SeedAsync( + new DataSeedContext(tenant.Id) + .WithProperty("AdminEmail", input.AdminEmailAddress) + .WithProperty("AdminPassword", input.AdminPassword) + ); } - + return ObjectMapper.Map(tenant); } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json index fc51f00750..d6d1d7c155 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json @@ -15,6 +15,8 @@ "Permission:Edit": "Edit", "Permission:Delete": "Delete", "Permission:ManageConnectionStrings": "Manage connection strings", - "Permission:ManageFeatures": "Manage features" + "Permission:ManageFeatures": "Manage features", + "DisplayName:AdminEmailAddress": "Admin Email Address", + "DisplayName:AdminPassword": "Admin Password" } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json index 1b6bdca2c2..72b570f3f9 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json @@ -15,6 +15,8 @@ "Permission:Edit": "Düzenleme", "Permission:Delete": "Silme", "Permission:ManageConnectionStrings": "Bağlantı cümlelerini yönet", - "Permission:ManageFeatures": "Özellikleri yönet" + "Permission:ManageFeatures": "Özellikleri yönet", + "DisplayName:AdminEmailAddress": "Admin Eposta Adresi", + "DisplayName:AdminPassword": "Admin Şifresi" } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs index 2aabf871de..df8e513003 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs @@ -35,6 +35,7 @@ namespace Volo.Abp.TenantManagement [HttpPost] public virtual Task CreateAsync(TenantCreateDto input) { + ValidateModel(); return _service.CreateAsync(input); } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml index b3a2c9d5be..8593679a7c 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml @@ -12,7 +12,11 @@ - + + + + + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs index a602b12a65..23c71bba96 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs @@ -31,8 +31,16 @@ namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants { [Required] [StringLength(TenantConsts.MaxNameLength)] - [Display(Name = "DisplayName:TenantName")] public string Name { get; set; } + + [Required] + [EmailAddress] + [MaxLength(256)] + public string AdminEmailAddress { get; set; } + + [Required] + [MaxLength(128)] + public string AdminPassword { get; set; } } } } diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs index 3e44e7c8ea..b27ceff6dc 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs @@ -59,7 +59,7 @@ namespace Volo.Abp.TenantManagement public async Task CreateAsync() { var tenancyName = Guid.NewGuid().ToString("N").ToLowerInvariant(); - var tenant = await _tenantAppService.CreateAsync(new TenantCreateDto { Name = tenancyName }); + var tenant = await _tenantAppService.CreateAsync(new TenantCreateDto { Name = tenancyName , AdminEmailAddress = "admin@admin.com", AdminPassword = "123456"}); tenant.Name.ShouldBe(tenancyName); tenant.Id.ShouldNotBe(default(Guid)); } @@ -69,7 +69,7 @@ namespace Volo.Abp.TenantManagement { await Assert.ThrowsAsync(async () => { - await _tenantAppService.CreateAsync(new TenantCreateDto { Name = "acme" }); + await _tenantAppService.CreateAsync(new TenantCreateDto { Name = "acme", AdminEmailAddress = "admin@admin.com", AdminPassword = "123456" }); }); } diff --git a/npm/ng-packs/apps/dev-app/src/app/shared/shared.module.ts b/npm/ng-packs/apps/dev-app/src/app/shared/shared.module.ts index 6c4c9b016c..1f268b4572 100644 --- a/npm/ng-packs/apps/dev-app/src/app/shared/shared.module.ts +++ b/npm/ng-packs/apps/dev-app/src/app/shared/shared.module.ts @@ -3,7 +3,6 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NgModule } from '@angular/core'; import { ThemeBasicModule } from '@abp/ng.theme.basic'; import { ThemeSharedModule } from '@abp/ng.theme.shared'; -import { TableModule } from 'primeng/table'; import { NgxValidateCoreModule } from '@ngx-validate/core'; @NgModule({ @@ -12,7 +11,6 @@ import { NgxValidateCoreModule } from '@ngx-validate/core'; CoreModule, ThemeSharedModule, ThemeBasicModule, - TableModule, NgbDropdownModule, NgxValidateCoreModule, ], @@ -20,7 +18,6 @@ import { NgxValidateCoreModule } from '@ngx-validate/core'; CoreModule, ThemeSharedModule, ThemeBasicModule, - TableModule, NgbDropdownModule, NgxValidateCoreModule, ], diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index e020b3f235..93ef5c8ae7 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -21,19 +21,19 @@ "generate:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" }, "devDependencies": { - "@abp/ng.account": "~2.1.0", - "@abp/ng.account.config": "~2.1.0", - "@abp/ng.core": "~2.1.0", - "@abp/ng.feature-management": "~2.1.0", - "@abp/ng.identity": "~2.1.0", - "@abp/ng.identity.config": "~2.1.0", - "@abp/ng.permission-management": "~2.1.0", - "@abp/ng.setting-management": "~2.1.0", - "@abp/ng.setting-management.config": "~2.1.0", - "@abp/ng.tenant-management": "~2.1.0", - "@abp/ng.tenant-management.config": "~2.1.0", - "@abp/ng.theme.basic": "~2.1.0", - "@abp/ng.theme.shared": "~2.1.0", + "@abp/ng.account": "~2.2.0", + "@abp/ng.account.config": "~2.2.0", + "@abp/ng.core": "~2.2.0", + "@abp/ng.feature-management": "~2.2.0", + "@abp/ng.identity": "~2.2.0", + "@abp/ng.identity.config": "~2.2.0", + "@abp/ng.permission-management": "~2.2.0", + "@abp/ng.setting-management": "~2.2.0", + "@abp/ng.setting-management.config": "~2.2.0", + "@abp/ng.tenant-management": "~2.2.0", + "@abp/ng.tenant-management.config": "~2.2.0", + "@abp/ng.theme.basic": "~2.2.0", + "@abp/ng.theme.shared": "~2.2.0", "@angular-builders/jest": "^8.2.0", "@angular-devkit/build-angular": "~0.803.21", "@angular-devkit/build-ng-packagr": "~0.803.21", diff --git a/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts b/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts index d8fe9762e1..fd66913ca5 100644 --- a/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts @@ -1,9 +1,9 @@ -import { ABP, SetTenant, SessionState } from '@abp/ng.core'; +import { ABP, SetTenant, SessionState, GetAppConfiguration } from '@abp/ng.core'; import { ToasterService } from '@abp/ng.theme.shared'; import { Component, OnInit } from '@angular/core'; import { Store } from '@ngxs/store'; import { throwError } from 'rxjs'; -import { catchError, take, finalize } from 'rxjs/operators'; +import { catchError, take, finalize, switchMap } from 'rxjs/operators'; import snq from 'snq'; import { AccountService } from '../../services/account.service'; import { Account } from '../../models/account'; @@ -52,29 +52,32 @@ export class TenantBoxComponent ); return throwError(err); }), + switchMap(({ success, tenantId }) => { + if (success) { + this.tenant = { + id: tenantId, + name: this.tenant.name, + }; + this.tenantName = this.tenant.name; + this.isModalVisible = false; + } else { + this.toasterService.error( + 'AbpUiMultiTenancy::GivenTenantIsNotAvailable', + 'AbpUi::Error', + { + messageLocalizationParams: [this.tenant.name], + }, + ); + this.tenant = {} as ABP.BasicItem; + this.tenantName = ''; + } + this.store.dispatch(new SetTenant(success ? this.tenant : null)); + return this.store.dispatch(new GetAppConfiguration()); + }), ) - .subscribe(({ success, tenantId }) => { - if (success) { - this.tenant = { - id: tenantId, - name: this.tenant.name, - }; - this.tenantName = this.tenant.name; - this.isModalVisible = false; - } else { - this.toasterService.error( - 'AbpUiMultiTenancy::GivenTenantIsNotAvailable', - 'AbpUi::Error', - { - messageLocalizationParams: [this.tenant.name], - }, - ); - this.tenant = {} as ABP.BasicItem; - } - this.store.dispatch(new SetTenant(success ? this.tenant : null)); - }); + .subscribe(); } else { - this.store.dispatch(new SetTenant(null)); + this.store.dispatch([new SetTenant(null), new GetAppConfiguration()]); this.tenantName = null; this.isModalVisible = false; } diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index f424d83cb2..5269946391 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -1,11 +1,12 @@ +import { Injectable } from '@angular/core'; import { Action, createSelector, Selector, State, StateContext, Store } from '@ngxs/store'; import { of } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import snq from 'snq'; import { + AddRoute, GetAppConfiguration, PatchRouteByName, - AddRoute, SetEnvironment, } from '../actions/config.actions'; import { SetLanguage } from '../actions/session.actions'; @@ -14,7 +15,6 @@ import { Config } from '../models/config'; import { ApplicationConfigurationService } from '../services/application-configuration.service'; import { organizeRoutes } from '../utils/route-utils'; import { SessionState } from './session.state'; -import { Injectable } from '@angular/core'; @State({ name: 'ConfigState', @@ -91,24 +91,24 @@ export class ConfigState { const selector = createSelector([ConfigState], (state: Config.State) => { return snq(() => state.setting.values[key]); }); + return selector; } static getSettings(keyword?: string) { const selector = createSelector([ConfigState], (state: Config.State) => { - if (keyword) { - const keys = snq( - () => Object.keys(state.setting.values).filter(key => key.indexOf(keyword) > -1), - [], - ); - - if (keys.length) { - return keys.reduce((acc, key) => ({ ...acc, [key]: state.setting.values[key] }), {}); - } - } + const settings = snq(() => state.setting.values, {}); + + if (!keyword) return settings; - return snq(() => state.setting.values, {}); + const keysFound = Object.keys(settings).filter(key => key.indexOf(keyword) > -1); + + return keysFound.reduce((acc, key) => { + acc[key] = settings[key]; + return acc; + }, {}); }); + return selector; } diff --git a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts index 635ca1d8b1..07baed4c14 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts @@ -1,12 +1,12 @@ import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; import { Store } from '@ngxs/store'; -import { ReplaySubject, timer, Subject, of } from 'rxjs'; +import clone from 'just-clone'; +import { of, ReplaySubject, timer } from 'rxjs'; +import { AddRoute, PatchRouteByName, SetLanguage } from '../actions'; +import { ABP } from '../models'; import { Config } from '../models/config'; import { ApplicationConfigurationService, ConfigStateService } from '../services'; import { ConfigState } from '../states'; -import { SetLanguage, PatchRouteByName, AddRoute } from '../actions'; -import clone from 'just-clone'; -import { ABP } from '../models'; export const CONFIG_STATE_DATA = { environment: { @@ -116,6 +116,7 @@ export const CONFIG_STATE_DATA = { }, setting: { values: { + 'Abp.Custom.SomeSetting': 'X', 'Abp.Localization.DefaultLanguage': 'en', }, }, @@ -218,14 +219,14 @@ describe('ConfigState', () => { }); describe('#getSettings', () => { - it('should return settings', () => { - expect(ConfigState.getSettings('Localization')(CONFIG_STATE_DATA)).toEqual({ - 'Abp.Localization.DefaultLanguage': 'en', - }); - - expect(ConfigState.getSettings('AllSettings')(CONFIG_STATE_DATA)).toEqual( - CONFIG_STATE_DATA.setting.values, - ); + test.each` + keyword | expected + ${undefined} | ${CONFIG_STATE_DATA.setting.values} + ${'Localization'} | ${{ 'Abp.Localization.DefaultLanguage': 'en' }} + ${'X'} | ${{}} + ${'localization'} | ${{}} + `('should return $expected when keyword is given as $keyword', ({ keyword, expected }) => { + expect(ConfigState.getSettings(keyword)(CONFIG_STATE_DATA)).toEqual(expected); }); }); diff --git a/npm/ng-packs/scripts/install-new-dependencies.ts b/npm/ng-packs/scripts/install-new-dependencies.ts index 52f93f23d4..156851f657 100644 --- a/npm/ng-packs/scripts/install-new-dependencies.ts +++ b/npm/ng-packs/scripts/install-new-dependencies.ts @@ -17,7 +17,7 @@ const updateAndInstall = async () => { ...packageJson.devDependencies, ...dependencies, ...peerDependencies, - ...{ [name]: `^${version}` }, + ...{ [name]: `~${version}` }, }; packageJson.devDependencies = Object.keys(packageJson.devDependencies) diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index 897e84c66e..c95c5a148f 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -16,6 +16,7 @@ + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/sl.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/sl.json new file mode 100644 index 0000000000..687d42579c --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/sl.json @@ -0,0 +1,6 @@ +{ + "culture": "sl", + "texts": { + "ManageYourProfile": "Upravljajte svojim profilom" + } +} \ No newline at end of file diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json new file mode 100644 index 0000000000..99586f01c9 --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "ManageYourProfile": "管理个人资料" + } +} \ No newline at end of file diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hant.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hant.json new file mode 100644 index 0000000000..ceea055597 --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hant", + "texts": { + "ManageYourProfile": "管理個人資料" + } +} \ No newline at end of file