From 04fcf903dfcb616864266c138b344527c2874fd5 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 13 Mar 2020 13:08:12 +0800 Subject: [PATCH 01/12] Backgroundworkerbase logger properties use concrete types as generic parameters --- .../BackgroundWorkers/BackgroundWorkerBase.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) 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()); From 18e50cfe73b4aad3580c4dc18fa6854d0e604552 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 13 Mar 2020 13:19:03 +0300 Subject: [PATCH 02/12] docs: add config state usage --- docs/en/UI/Angular/Config-State.md | 288 +++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 docs/en/UI/Angular/Config-State.md diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md new file mode 100644 index 0000000000..39dea360b6 --- /dev/null +++ b/docs/en/UI/Angular/Config-State.md @@ -0,0 +1,288 @@ +# 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. + +```typescript +@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: + +```typescript +// 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. + +```typescript +// 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`: + +```typescript +const tenantId = this.config.getDeep("currentUser.tenantId"); +``` + +or by giving an array of keys as parameter: + +```typescript +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: + +```typescript +// 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: + +```typescript +// 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: + +```typescript +// 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. + +```typescript +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: + +```typescript +// 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. + +```typescript +// 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: + +```typescript +// 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: + +```typescript +// 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. + +```typescript +// 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. + +```typescript +// 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 `dispatchPatchRouteByName` triggers a state method that finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter. + +```typescript +// 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:** + +```typescript +// 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: + +```typescript +// 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). From dfd7c86db86fa2288a6f4e4dd882b8397187a5b6 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 13 Mar 2020 14:49:39 +0300 Subject: [PATCH 03/12] docs: add new route for config state doc --- docs/en/UI/Angular/Config-State.md | 38 ++++++++++++++++-------------- docs/en/docs-nav.json | 4 ++++ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md index 39dea360b6..77885d0e05 100644 --- a/docs/en/UI/Angular/Config-State.md +++ b/docs/en/UI/Angular/Config-State.md @@ -6,7 +6,9 @@ In order to use the `ConfigStateService` you must inject it in your class as a dependency. -```typescript +```js +import { ConfigStateService } from '@abp/ng.core'; + @Component({ /* class metadata here */ }) @@ -25,7 +27,7 @@ You do not have to provide the `ConfigStateService` at module or component/direc You can use the `getAll` method of `ConfigStateService` to get all of the configuration object from the store. It is used as follows: -```typescript +```js // this.config is instance of ConfigStateService const config = this.config.getAll(); @@ -35,7 +37,7 @@ const config = this.config.getAll(); 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. -```typescript +```js // this.config is instance of ConfigStateService const currentUser = this.config.getOne("currentUser"); @@ -43,13 +45,13 @@ 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`: -```typescript +```js const tenantId = this.config.getDeep("currentUser.tenantId"); ``` or by giving an array of keys as parameter: -```typescript +```js const tenantId = this.config.getDeep(["currentUser", "tenantId"]); ``` @@ -63,7 +65,7 @@ Please refer to `Config.State` type for all the properties you can get with `get 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: -```typescript +```js // this.config is instance of ConfigStateService const appInfo = this.config.getApplicationInfo(); @@ -79,7 +81,7 @@ Please refer to `Config.Application` type for all the properties you can get wit 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: -```typescript +```js // this.config is instance of ConfigStateService const apiUrl = this.config.getApiUrl(); @@ -95,7 +97,7 @@ This method returns the `url` of a specific API based on the key given as its on 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: -```typescript +```js // this.config is instance of ConfigStateService const settings = this.config.getSettings(); @@ -103,7 +105,7 @@ const settings = this.config.getSettings(); In addition, the method lets you search settings by **passing a keyword** to it. -```typescript +```js const localizationSettings = this.config.getSettings("Localization"); /* { @@ -118,7 +120,7 @@ Beware though, **settings search is case sensitive**. You can use the `getSetting` method of `ConfigStateService` to get a specific setting from the configuration state. Here is an example: -```typescript +```js // this.config is instance of ConfigStateService const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage"); @@ -129,7 +131,7 @@ const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage"); 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. -```typescript +```js // this.config is instance of ConfigStateService const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity"); @@ -138,7 +140,7 @@ const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity"); You may also **combine policy keys** to fine tune your selection: -```typescript +```js // this.config is instance of ConfigStateService const hasIdentityAndAccountPermission = this.config.getGrantedPolicy( @@ -164,7 +166,7 @@ Please consider the following **rules** when creating your permission selectors: The `getLocalization` method of `ConfigStateService` is used for translations. Here are some examples: -```typescript +```js // this.config is instance of ConfigStateService const identity = this.config.getLocalization("AbpIdentity::Identity"); @@ -190,7 +192,7 @@ Please check out the [localization documentation](./Localization.md) for details 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. -```typescript +```js // this.config is instance of ConfigStateService this.config.dispatchGetAppConfiguration(); @@ -203,7 +205,7 @@ Note that **you do not have to call this method at application initiation**, bec The `dispatchPatchRouteByName` finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter. -```typescript +```js // this.config is instance of ConfigStateService const newRouteConfig: Partial = { @@ -225,7 +227,7 @@ this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig); The `dispatchPatchRouteByName` triggers a state method that finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter. -```typescript +```js // this.config is instance of ConfigStateService const newRoute: ABP.Route = { @@ -245,7 +247,7 @@ The `newRoute` will be placed as at root level, i.e. without any parent routes a If you want **to add a child route, you can do this:** -```typescript +```js // this.config is instance of ConfigStateService const newRoute: ABP.Route = { @@ -272,7 +274,7 @@ Please refer to `ABP.Route` type for all the properties you can pass to `dispatc The `dispatchSetEnvironment` places environment variables passed to it in the `Store` under the configuration state. Here is how it is used: -```typescript +```js // this.config is instance of ConfigStateService this.config.dispatchSetEnvironment({ 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" From c8663c88af76dcdc046d304576f36d4ab7088634 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 13 Mar 2020 14:58:22 +0300 Subject: [PATCH 04/12] docs: fix dispatchAddRoute description --- docs/en/UI/Angular/Config-State.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md index 77885d0e05..72c8e6c6fd 100644 --- a/docs/en/UI/Angular/Config-State.md +++ b/docs/en/UI/Angular/Config-State.md @@ -225,7 +225,7 @@ this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig); ### How to Add a New Route Configuration -The `dispatchPatchRouteByName` triggers a state method that finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter. +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 From 537fb1a4946d36bbddff3b21b70d3e3d43f26a3a Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 13 Mar 2020 20:31:09 +0800 Subject: [PATCH 05/12] Add localization files to module templates. Fix #3105 --- .../Localization/MyProjectName/sl.json | 6 ++++++ .../Localization/MyProjectName/zh-Hans.json | 6 ++++++ .../Localization/MyProjectName/zh-Hant.json | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/sl.json create mode 100644 templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json create mode 100644 templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hant.json 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 From 7a69047b8165ef498c411825a03c551683521cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 13 Mar 2020 15:48:56 +0300 Subject: [PATCH 06/12] Complete the Customization-User-Interface document --- .../Customization-User-Interface.md | 34 +++++++++++++++++-- .../BasicTheme.cs | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/en/UI/AspNetCore/Customization-User-Interface.md b/docs/en/UI/AspNetCore/Customization-User-Interface.md index 7e49efd602..ef4d1e97f7 100644 --- a/docs/en/UI/AspNetCore/Customization-User-Interface.md +++ b/docs/en/UI/AspNetCore/Customization-User-Interface.md @@ -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. \ No newline at end of file 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) { From 48e38d4d841a0eb991004e38aa0966abd1ada378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 13 Mar 2020 15:56:48 +0300 Subject: [PATCH 07/12] Resolved #3056: Guide to customize the pre-build application modules. --- docs/en/Authorization.md | 4 ++++ docs/en/Customizing-Application-Modules-Guide.md | 7 +++++++ 2 files changed, 11 insertions(+) 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 From fc00f57d96713a13b616963fc5cc5db7f98b740c Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 13 Mar 2020 16:46:01 +0300 Subject: [PATCH 08/12] fix: return empty object from getSettings when keyword is not found --- .../core/src/lib/states/config.state.ts | 30 ++++++++----------- .../core/src/lib/tests/config.state.spec.ts | 25 ++++++++-------- 2 files changed, 26 insertions(+), 29 deletions(-) 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..5fed4b9816 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', @@ -88,28 +88,24 @@ export class ConfigState { } static getSetting(key: string) { - const selector = createSelector([ConfigState], (state: Config.State) => { + return 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] }), {}); - } - } + return createSelector([ConfigState], (state: Config.State) => { + const settings = snq(() => state.setting.values, {}); - return snq(() => state.setting.values, {}); + if (!keyword) return settings; + + const keysFound = Object.keys(settings).filter(key => key.indexOf(keyword) > -1); + + return keysFound.reduce((acc, key) => { + acc[key] = settings[key]; + return acc; + }, {}); }); - return selector; } static getGrantedPolicy(key: string) { 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); }); }); From 7035168861cef6e1913113e331cfb2c5dc47768e Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 13 Mar 2020 16:56:05 +0300 Subject: [PATCH 09/12] fix: assign createSelector return value to a variable before returning (ng-packagr) --- npm/ng-packs/packages/core/src/lib/states/config.state.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 5fed4b9816..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 @@ -88,13 +88,15 @@ export class ConfigState { } static getSetting(key: string) { - return createSelector([ConfigState], (state: Config.State) => { + const selector = createSelector([ConfigState], (state: Config.State) => { return snq(() => state.setting.values[key]); }); + + return selector; } static getSettings(keyword?: string) { - return createSelector([ConfigState], (state: Config.State) => { + const selector = createSelector([ConfigState], (state: Config.State) => { const settings = snq(() => state.setting.values, {}); if (!keyword) return settings; @@ -106,6 +108,8 @@ export class ConfigState { return acc; }, {}); }); + + return selector; } static getGrantedPolicy(key: string) { From 59a149457bf8c9afa32c41c9e4ff033184e4abac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 13 Mar 2020 17:35:59 +0300 Subject: [PATCH 10/12] Fixed #3117: Physical razor files are not overridding the virtual files --- .../AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) 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..? From 2c76eb2734cce39f3e464a2271b812ff9ccf33e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 13 Mar 2020 22:05:33 +0300 Subject: [PATCH 11/12] Create IdentityServer-Integration.md --- docs/en/IdentityServer-Integration.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/en/IdentityServer-Integration.md 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 From 7b4894bbe31329e146b8cfb6c40c26a5e745e42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 13 Mar 2020 22:45:01 +0300 Subject: [PATCH 12/12] Update Blogging.md --- docs/en/Modules/Blogging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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