Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into dev

pull/3122/head
mehmet-erim 7 years ago
parent
commit
38506acf19
  1. 4
      docs/en/Authorization.md
  2. 7
      docs/en/Customizing-Application-Modules-Guide.md
  3. 3
      docs/en/IdentityServer-Integration.md
  4. 2
      docs/en/Modules/Blogging.md
  5. 290
      docs/en/UI/Angular/Config-State.md
  6. 34
      docs/en/UI/AspNetCore/Customization-User-Interface.md
  7. 4
      docs/en/docs-nav.json
  8. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/BasicTheme.cs
  9. 26
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  10. 30
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs
  11. 26
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  12. 25
      npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts
  13. 6
      templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/sl.json
  14. 6
      templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json
  15. 6
      templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hant.json

4
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.

7
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.

3
docs/en/IdentityServer-Integration.md

@ -0,0 +1,3 @@
# IdentityServer Integration
TODO

2
docs/en/Modules/Blogging.md

@ -1,3 +1,3 @@
# IdentityServer Module
# Blogging Module
TODO

290
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<ABP.Route> = {
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).

34
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
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.

4
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"

2
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)
{

26
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<IConfigureOptions<MvcRazorRuntimeCompilationOptions>>(
new ConfigureOptions<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Add(
new RazorViewEngineVirtualFileProvider(
context.Services.GetSingletonInstance<IObjectAccessor<IServiceProvider>>()
)
);
}
)
)
);
Configure<AbpApiDescriptionModelOptions>(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<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Add(
new RazorViewEngineVirtualFileProvider(
context.Services.GetSingletonInstance<IObjectAccessor<IServiceProvider>>()
)
);
});
context.Services.ExecutePreConfiguredActions(mvcBuilder);
//TODO: AddViewLocalization by default..?

30
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<BackgroundWorkerBase> Logger { protected get; set; }
protected TService LazyGetRequiredService<TService>(ref TService reference)
=> LazyGetRequiredService(typeof(TService), ref reference);
protected BackgroundWorkerBase()
protected TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
{
Logger = NullLogger<BackgroundWorkerBase>.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<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
public virtual Task StartAsync(CancellationToken cancellationToken = default)
{
Logger.LogDebug("Started background worker: " + ToString());

26
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<Config.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;
}

25
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);
});
});

6
templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/sl.json

@ -0,0 +1,6 @@
{
"culture": "sl",
"texts": {
"ManageYourProfile": "Upravljajte svojim profilom"
}
}

6
templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json

@ -0,0 +1,6 @@
{
"culture": "zh-Hans",
"texts": {
"ManageYourProfile": "管理个人资料"
}
}

6
templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hant.json

@ -0,0 +1,6 @@
{
"culture": "zh-Hant",
"texts": {
"ManageYourProfile": "管理個人資料"
}
}
Loading…
Cancel
Save