@ -1,3 +1,140 @@ |
|||||
# Background Workers |
# Background Workers |
||||
|
|
||||
TODO |
## Introduction |
||||
|
|
||||
|
Background workers are simple independent threads in the application running in the background. Generally, they run periodically to perform some tasks. Examples; |
||||
|
|
||||
|
* A background worker can run periodically to **delete old logs**. |
||||
|
* A background worker can run periodically to **determine inactive users** and **send emails** to get users to return to your application. |
||||
|
|
||||
|
|
||||
|
## Create a Background Worker |
||||
|
|
||||
|
A background worker should directly or indirectly implement the `IBackgroundWorker` interface. |
||||
|
|
||||
|
> A background worker is inherently [singleton](Dependency-Injection.md). So, only a single instance of your worker class is instantiated and run. |
||||
|
|
||||
|
### BackgroundWorkerBase |
||||
|
|
||||
|
`BackgroundWorkerBase` is an easy way to create a background worker. |
||||
|
|
||||
|
````csharp |
||||
|
public class MyWorker : BackgroundWorkerBase |
||||
|
{ |
||||
|
public override Task StartAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
|
||||
|
public override Task StopAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
Start your worker in the `StartAsync` (which is called when the application begins) and stop in the `StopAsync` (which is called when the application shuts down). |
||||
|
|
||||
|
> You can directly implement the `IBackgroundWorker`, but `BackgroundWorkerBase` provides some useful properties like `Logger`. |
||||
|
|
||||
|
### AsyncPeriodicBackgroundWorkerBase |
||||
|
|
||||
|
Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. `AsyncPeriodicBackgroundWorkerBase` class simplifies to create periodic workers, so we will use it for the example below: |
||||
|
|
||||
|
````csharp |
||||
|
public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase |
||||
|
{ |
||||
|
public PassiveUserCheckerWorker( |
||||
|
AbpTimer timer, |
||||
|
IServiceScopeFactory serviceScopeFactory |
||||
|
) : base( |
||||
|
timer, |
||||
|
serviceScopeFactory) |
||||
|
{ |
||||
|
Timer.Period = 600000; //10 minutes |
||||
|
} |
||||
|
|
||||
|
protected override async Task DoWorkAsync( |
||||
|
PeriodicBackgroundWorkerContext workerContext) |
||||
|
{ |
||||
|
Logger.LogInformation("Starting: Setting status of inactive users..."); |
||||
|
|
||||
|
//Resolve dependencies |
||||
|
var userRepository = workerContext |
||||
|
.ServiceProvider |
||||
|
.GetRequiredService<IUserRepository>(); |
||||
|
|
||||
|
//Do the work |
||||
|
await userRepository.UpdateInactiveUserStatusesAsync(); |
||||
|
|
||||
|
Logger.LogInformation("Completed: Setting status of inactive users..."); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* `AsyncPeriodicBackgroundWorkerBase` uses the `AbpTimer` (a thread-safe timer) object to determine **the period**. We can set its `Period` property in the constructor. |
||||
|
* It required to implement the `DoWorkAsync` method to **execute** the periodic work. |
||||
|
* It is a good practice to **resolve dependencies** from the `PeriodicBackgroundWorkerContext` instead of constructor injection. Because `AsyncPeriodicBackgroundWorkerBase` uses a `IServiceScope` that is **disposed** when your work finishes. |
||||
|
* `AsyncPeriodicBackgroundWorkerBase` **catches and logs exceptions** thrown by the `DoWorkAsync` method. |
||||
|
|
||||
|
|
||||
|
## Register Background Worker |
||||
|
|
||||
|
After creating a background worker class, you should to add it to the `IBackgroundWorkerManager`. The most common place is the `OnApplicationInitialization` method of your module class: |
||||
|
|
||||
|
````csharp |
||||
|
[DependsOn(typeof(AbpBackgroundWorkersModule))] |
||||
|
public class MyModule : AbpModule |
||||
|
{ |
||||
|
public override void OnApplicationInitialization( |
||||
|
ApplicationInitializationContext context) |
||||
|
{ |
||||
|
context.AddBackgroundWorker<PassiveUserCheckerWorker>(); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`context.AddBackgroundWorker(...)` is a shortcut extension method for the expression below: |
||||
|
|
||||
|
````csharp |
||||
|
context.ServiceProvider |
||||
|
.GetRequiredService<IBackgroundWorkerManager>() |
||||
|
.Add( |
||||
|
context |
||||
|
.ServiceProvider |
||||
|
.GetRequiredService<PassiveUserCheckerWorker>() |
||||
|
); |
||||
|
```` |
||||
|
|
||||
|
So, it resolves the given background worker and adds to the `IBackgroundWorkerManager`. |
||||
|
|
||||
|
While we generally add workers in `OnApplicationInitialization`, there are no restrictions on that. You can inject `IBackgroundWorkerManager` anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down. |
||||
|
|
||||
|
## Options |
||||
|
|
||||
|
`AbpBackgroundWorkerOptions` class is used to [set options](Options.md) for the background workers. Currently, there is only one option: |
||||
|
|
||||
|
* `IsEnabled` (default: true): Used to **enable/disable** the background worker system for your application. |
||||
|
|
||||
|
> See the [Options](Options.md) document to learn how to set options. |
||||
|
|
||||
|
## Making Your Application Always Run |
||||
|
|
||||
|
Background workers only work if your application is running. If you host the background job execution in your web application (this is the default behavior), you should ensure that your web application is configured to always be running. Otherwise, background jobs only work while your application is in use. |
||||
|
|
||||
|
## Running On a Cluster |
||||
|
|
||||
|
Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example). |
||||
|
|
||||
|
If that's a problem for your workers, you have two options; |
||||
|
|
||||
|
* Disable the background worker system using the `AbpBackgroundWorkerOptions` described above, for all the application instances, except one of them. |
||||
|
* Disable the background worker system for all the application instances and create another special application that runs on a single server and execute the workers. |
||||
|
|
||||
|
## Quartz Integration |
||||
|
|
||||
|
ABP Framework's background worker system is good to implement periodic tasks. However, you may want to use an advanced task scheduler like [Quartz](https://www.quartz-scheduler.net/). See the community contributed [quartz integration](Background-Workers-Quartz.md) for the background workers. |
||||
|
|
||||
|
## See Also |
||||
|
* [Quartz Integration for the background workers](Background-Workers-Quartz.md) |
||||
|
* [Background Jobs](Background-Jobs.md) |
||||
@ -0,0 +1,142 @@ |
|||||
|
# ABP Framework v2.3.0 Has Been Released! |
||||
|
|
||||
|
In the days of **coronavirus**, we have released **ABP Framework v2.3** and this post will explain **what's new** with this release and **what we've done** in the last two weeks. |
||||
|
|
||||
|
## About the Coronavirus & Our Team |
||||
|
|
||||
|
**We are very sad** about the coronavirus case. As [Volosoft](https://volosoft.com/) team, we have **remote workers** working in their home in different countries. Beginning from the last week, we've **completely started to work remotely** from home including our main office employees. |
||||
|
|
||||
|
We believe in and pray for that the humanity will overcome this issue in a short time. |
||||
|
|
||||
|
## About the Release Cycle |
||||
|
|
||||
|
Beginning from the ABP v2.1.0, we have started to release feature versions once **in two weeks**, on Thursdays. This is the 3rd release after that decision and we see that it works fine for now and improved our agility. |
||||
|
|
||||
|
We will continue to release **feature versions** (like v2.4, v2.5) in every two weeks. In addition, we may release **hotfix versions** (like v2.3.1, v2.3.2) whenever needed. |
||||
|
|
||||
|
## What's New in ABP Framework v2.3.0 |
||||
|
|
||||
|
We've completed & merged **[104](https://github.com/abpframework/abp/milestone/30?closed=1) issues and pull requests** with **393 commits** in this two weeks development period. |
||||
|
|
||||
|
I will introduce some new features and enhancements introduced with this release. |
||||
|
|
||||
|
### React Native Mobile Application |
||||
|
|
||||
|
We have finally completed the **react native mobile application**. It currently allows you to **login**, manage your **users** and **tenants**. It utilizes the same setting, authorization and localization systems of the ABP Framework. |
||||
|
|
||||
|
A few screenshots from the application: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
It doesn't have much functionality but it is a **perfect starting point** for your own mobile application since it is completely integrated to the backend and supports multi-tenancy. |
||||
|
|
||||
|
### Angular TypeScript Proxy Generator |
||||
|
|
||||
|
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on he server side) and **model objects** (matches to [DTOs](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects) in the server side). |
||||
|
|
||||
|
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced: |
||||
|
|
||||
|
* It generates a **big, single** .ts file which has some problems; |
||||
|
* It get **too large** when your application grows. |
||||
|
* It doesn't fit into the **[modular](https://docs.abp.io/en/abp/latest/Module-Development-Basics) approach** of the ABP framework. |
||||
|
* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually). |
||||
|
* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies. |
||||
|
|
||||
|
So, we've decided to create an ABP CLI command to automatically generate the typescript client proxies ([#2222](https://github.com/abpframework/abp/issues/2222)) for your REST API developed with the ABP Framework. |
||||
|
|
||||
|
It is easy to use. Just run the following command in the **root folder** of the angular application: |
||||
|
|
||||
|
````bash |
||||
|
abp generate-proxy |
||||
|
```` |
||||
|
|
||||
|
It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](https://docs.abp.io/en/abp/latest/CLI). |
||||
|
|
||||
|
### CRUD Application Services for Entities with Composite Keys |
||||
|
|
||||
|
` CrudAppService ` is a useful base class to create CRUD application services for your entities. But it doesn't support entities with **composite primary keys**. `AbstractKeyCrudAppService` is the new base class that is developed to support entities with composite primary keys. See [the documentation](https://docs.abp.io/en/abp/latest/Application-Services#abstractkeycrudappservice) for more. |
||||
|
|
||||
|
### Add Source Code of the Modules |
||||
|
|
||||
|
The application startup template comes with some [application modules](https://docs.abp.io/en/abp/latest/Modules/Index) **pre-installed** as **NuGet & NPM packages**. This have a few important advantages: |
||||
|
|
||||
|
* You can **easily [upgrade](https://docs.abp.io/en/abp/latest/CLI#update)** these modules when a new version is available. |
||||
|
* Your solution becomes **cleaner**, so you can focus on your own code. |
||||
|
|
||||
|
However, when you need to make **major customizations** for a depended module, it is not easy as its source code is in your applications. To solve this problem, we've introduces a new command to the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) that **replaces** NuGet packages with their **source code** in your solution. The usage is simple: |
||||
|
|
||||
|
````bash |
||||
|
abp add-module --with-source-code |
||||
|
```` |
||||
|
|
||||
|
This command adds a module with source code or replaces with its source code if it is already added as package references. |
||||
|
|
||||
|
> It is suggested to **save your changes** to your source control system before using this command since it makes a lot of changes in your source code. |
||||
|
|
||||
|
In addition, we've documented how to customize depended modules without changing their source code (see the section below). It is suggested to use modules as packages to easily upgrade them in the future. |
||||
|
|
||||
|
> Source code of the free modules are licensed under **MIT**, so you can freely change them and add into your solution. |
||||
|
|
||||
|
### Switch to Preview |
||||
|
|
||||
|
ABP Framework is rapidly evolving and we are frequently releasing new versions. However, if you want to follow it closer, you can use the **daily preview packages**. |
||||
|
|
||||
|
We've created an ABP CLI command to easily **update to the latest preview packages** for your solution. Run the following command in the root folder of your solution: |
||||
|
|
||||
|
````bash |
||||
|
abp switch-to-preview |
||||
|
```` |
||||
|
|
||||
|
It will change the versions of all ABP related NuGet and NPM packages. You can **switch back to the latest stable** when you want: |
||||
|
|
||||
|
````bash |
||||
|
abp switch-to-stable |
||||
|
```` |
||||
|
|
||||
|
See the [ABP CLI document](https://docs.abp.io/en/abp/latest/CLI#switch-to-preview) fore more. |
||||
|
|
||||
|
### Documentation Improvements |
||||
|
|
||||
|
#### Extending/Customizing Depended Application Modules |
||||
|
|
||||
|
We've created a huge documentation that explains how to customize a depended module without changing its source code. See [the documentation](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Guide). |
||||
|
|
||||
|
In addition to the documentation, we've revised all the modules ([#3166](https://github.com/abpframework/abp/issues/3166)) to make their services easily extensible & customizable. |
||||
|
|
||||
|
#### EF Core Migration Guide |
||||
|
|
||||
|
We've recently created a guide to explain the migration system that is used by the ABP startup templates. [This guide](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Migrations) also explains how to customize the migration structure, split your modules across multiple databases, reusing a module's table and son on. |
||||
|
|
||||
|
#### Migration from the ASP.NET Boilerplate |
||||
|
|
||||
|
If you have a solution built on the ASP.NET Boilerplate, we've [created a guide](https://docs.abp.io/en/abp/latest/AspNet-Boilerplate-Migration-Guide) that tries to help you if you want to migrate your solution to the new ABP Framework. |
||||
|
|
||||
|
### Some Other Features |
||||
|
|
||||
|
#### The Framework |
||||
|
|
||||
|
* Add `IRepository.GetAsync` and `IRepository.FindAsync` methods ([#3184](https://github.com/abpframework/abp/issues/3148)). |
||||
|
|
||||
|
#### Modules |
||||
|
|
||||
|
* Get password & email address of the admin while creating a new tenant, for the tenant management module ([#3088](https://github.com/abpframework/abp/issues/3088)). |
||||
|
* Elastic search integrated full text search for the docs module ([#2901](https://github.com/abpframework/abp/pull/2901)). |
||||
|
* New Quartz background worker module ([#2762](https://github.com/abpframework/abp/issues/2762)) |
||||
|
|
||||
|
#### Samples |
||||
|
|
||||
|
* Add multi-tenancy support to the microservice demo ([#3032](https://github.com/abpframework/abp/pull/3032)). |
||||
|
|
||||
|
See [the release notes](https://github.com/abpframework/abp/releases/tag/2.3.0) for all feature, enhancement and bugfixes. |
||||
|
|
||||
|
## What's Next? |
||||
|
|
||||
|
We have the following goals for the next few months: |
||||
|
|
||||
|
* Complete the **documentation and samples**, write more tutorials. |
||||
|
* Make the framework and existing modules more **customizable and extensible**. |
||||
|
* Integrate to **gRPC** & implement gRPC endpoint for pre-built modules ([#2882](https://github.com/abpframework/abp/issues/2882)). |
||||
|
* Create a **Blazor UI** for the ABP Framework & implement it for all the modules and startup templates ([#394](https://github.com/abpframework/abp/issues/394)). |
||||
|
* Add **new features** to pre-built modules and create new modules for the [ABP Commercial](https://commercial.abp.io/). |
||||
|
|
||||
|
See [the GitHub milestones](https://github.com/abpframework/abp/milestones) for details. |
||||
|
After Width: | Height: | Size: 541 KiB |
|
After Width: | Height: | Size: 179 KiB |
@ -0,0 +1,3 @@ |
|||||
|
# IdentityServer Integration |
||||
|
|
||||
|
TODO |
||||
@ -1,3 +1,3 @@ |
|||||
# IdentityServer Module |
# Blogging Module |
||||
|
|
||||
TODO |
TODO |
||||
@ -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). |
||||
@ -0,0 +1,209 @@ |
|||||
|
# How to Make HTTP Requests |
||||
|
|
||||
|
|
||||
|
|
||||
|
## About HttpClient |
||||
|
|
||||
|
Angular has the amazing [HttpClient](https://angular.io/guide/http) for communication with backend services. It is a layer on top and a simplified representation of [XMLHttpRequest Web API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). It also is the recommended agent by Angular for any HTTP request. There is nothing wrong with using the `HttpClient` in your ABP project. |
||||
|
|
||||
|
However, `HttpClient` leaves error handling to the caller (method). In other words, HTTP errors are handled manually and by hooking into the observer of the `Observable` returned. |
||||
|
|
||||
|
```js |
||||
|
getConfig() { |
||||
|
this.http.get(this.configUrl).subscribe( |
||||
|
config => this.updateConfig(config), |
||||
|
error => { |
||||
|
// Handle error here |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Although clear and flexible, handling errors this way is repetitive work, even when error processing is delegated to the store or any other injectable. |
||||
|
|
||||
|
An `HttpInterceptor` is able to catch `HttpErrorResponse` and can be used for a centralized error handling. Nevertheless, cases where default error handler, therefore the interceptor, must be disabled require additional work and comprehension of Angular internals. Check [this issue](https://github.com/angular/angular/issues/20203) for details. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## RestService |
||||
|
|
||||
|
ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by deafult. |
||||
|
|
||||
|
|
||||
|
|
||||
|
### Getting Started with RestService |
||||
|
|
||||
|
In order to use the `RestService`, you must inject it in your class as a dependency. |
||||
|
|
||||
|
```js |
||||
|
import { RestService } from '@abp/ng.core'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoService { |
||||
|
constructor(private rest: RestService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
You do not have to provide the `RestService` at module or component/directive level, because it is already **provided in root**. |
||||
|
|
||||
|
|
||||
|
|
||||
|
### How to Make a Request with RestService |
||||
|
|
||||
|
You can use the `request` method of the `RestService` is for HTTP requests. Here is an example: |
||||
|
|
||||
|
```js |
||||
|
getFoo(id: number) { |
||||
|
const request: Rest.Request<null> = { |
||||
|
method: 'GET', |
||||
|
url: '/api/some/path/to/foo/' + id, |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<null, FooResponse>(request); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
The `request` method always returns an `Observable<T>`. Therefore you can do the following wherever you use `getFoo` method: |
||||
|
|
||||
|
```js |
||||
|
doSomethingWithFoo(id: number) { |
||||
|
this.demoService.getFoo(id).subscribe( |
||||
|
foo => { |
||||
|
// Do something with foo. |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
**You do not have to worry about unsubscription.** The `RestService` uses `HttpClient` behind the scenes, so every observable it returns is a finite observable, i.e. it closes subscriptions automatically upon success or error. |
||||
|
|
||||
|
|
||||
|
|
||||
|
As you see, `request` method gets a request options object with `Rest.Request<T>` type. This generic type expects the interface of the request body. You may pass `null` when there is no body, like in a `GET` or a `DELETE` request. Here is an example where there is one: |
||||
|
|
||||
|
```js |
||||
|
postFoo(body: Foo) { |
||||
|
const request: Rest.Request<Foo> = { |
||||
|
method: 'POST', |
||||
|
url: '/api/some/path/to/foo', |
||||
|
body |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<Foo, FooResponse>(request); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
You may [check here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23) for complete `Rest.Request<T>` type, which has only a few chages compared to [HttpRequest](https://angular.io/api/common/http/HttpRequest) class in Angular. |
||||
|
|
||||
|
|
||||
|
|
||||
|
### How to Disable Default Error Handler of RestService |
||||
|
|
||||
|
The `request` method, used with defaults, always handles errors. Let's see how you can change that behavior and handle errors yourself: |
||||
|
|
||||
|
```js |
||||
|
deleteFoo(id: number) { |
||||
|
const request: Rest.Request<null> = { |
||||
|
method: 'DELETE', |
||||
|
url: '/api/some/path/to/foo/' + id, |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<null, void>(request, { skipHandleError: true }); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
`skipHandleError` config option, when set to `true`, disables the error handler and the returned observable starts throwing an error that you can catch in your subscription. |
||||
|
|
||||
|
```js |
||||
|
removeFooFromList(id: number) { |
||||
|
this.demoService.deleteFoo(id).subscribe( |
||||
|
foo => { |
||||
|
// Do something with foo. |
||||
|
}, |
||||
|
error => { |
||||
|
// Do something with error. |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
### How to Get a Specific API Endpoint From Application Config |
||||
|
|
||||
|
Another nice config option that `request` method receives is `apiName` (available as of v2.4), which can be used to get a specific module endpoint from application configuration. |
||||
|
|
||||
|
|
||||
|
|
||||
|
```js |
||||
|
putFoo(body: Foo, id: string) { |
||||
|
const request: Rest.Request<Foo> = { |
||||
|
method: 'PUT', |
||||
|
url: '/' + id, |
||||
|
body |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<Foo, void>(request, {apiName: 'foo'}); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
`putFoo` above will request `https://localhost:44305/api/some/path/to/foo/{id}` as long as the environment variables are as follows: |
||||
|
|
||||
|
```js |
||||
|
// environment.ts |
||||
|
|
||||
|
export const environment = { |
||||
|
apis: { |
||||
|
default: { |
||||
|
url: 'https://localhost:44305', |
||||
|
}, |
||||
|
foo: { |
||||
|
url: 'https://localhost:44305/api/some/path/to/foo', |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
/* rest of the environment variables here */ |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
### How to Observe Response Object or HTTP Events Instead of Body |
||||
|
|
||||
|
`RestService` assumes you are generally interested in the body of a response and, by default, sets `observe` property as `'body'`. However, there may be times you are rather interested in something else, such as a custom proprietary header. For that, the `request` method receives `observe` property in its config object. |
||||
|
|
||||
|
```js |
||||
|
getSomeCustomHeaderValue() { |
||||
|
const request: Rest.Request<null> = { |
||||
|
method: 'GET', |
||||
|
url: '/api/some/path/that/sends/some-custom-header', |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<null, HttpResponse<any>>( |
||||
|
request, |
||||
|
{observe: Rest.Observe.Response}, |
||||
|
).pipe( |
||||
|
map(response => response.headers.get('Some-Custom-Header')) |
||||
|
); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10). |
||||
|
|
||||
|
## What's Next? |
||||
|
|
||||
|
* [Localization](./Localization.md) |
||||
@ -0,0 +1,114 @@ |
|||||
|
# Easy TrackByFunction Implementation |
||||
|
|
||||
|
`TrackByService` is a utility service to provide an easy implementation for one of the most frequent needs in Angular templates: `TrackByFunction`. Please see [this page in Angular docs](https://angular.io/guide/template-syntax#ngfor-with-trackby) for its purpose. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Getting Started |
||||
|
|
||||
|
You do not have to provide the `TrackByService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components. For better type support, you may pass in the type of the iterated item to it. |
||||
|
|
||||
|
```js |
||||
|
import { TrackByService } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
list: Item[]; |
||||
|
|
||||
|
constructor(public readonly track: TrackByService<Item>) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
> Noticed `track` is `public` and `readonly`? That is because we will see some examples where methods of `TrackByService` instance are directly called in the component's template. That may be considered as an anti-pattern, but it has its own advantage, especially when component inheritance is leveraged. You can always use public component properties instead. |
||||
|
|
||||
|
|
||||
|
|
||||
|
**The members are also exported as separate functions.** If you do not want to inject `TrackByService`, you can always import and use those functions directly in your classes. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
There are two approaches available. |
||||
|
|
||||
|
1. You may inject `TrackByService` to your component and use its members. |
||||
|
2. You may use exported higher-order functions directly on component properties. |
||||
|
|
||||
|
|
||||
|
|
||||
|
### How to Track Items by a Key |
||||
|
|
||||
|
You can use `by` to get a `TrackByFunction` that tracks the iterated object based on one of its keys. For type support, you may pass in the type of the iterated item to it. |
||||
|
|
||||
|
```html |
||||
|
<!-- template of DemoComponent --> |
||||
|
|
||||
|
<div *ngFor="let item of list; trackBy: track.by('id')">{%{{{ item.name }}}%}</div> |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
`by` is exported as a stand-alone function and is named `trackBy`. |
||||
|
|
||||
|
```js |
||||
|
import { trackBy } from "@abp/ng.core"; |
||||
|
|
||||
|
@Component({ |
||||
|
template: ` |
||||
|
<div |
||||
|
*ngFor="let item of list; trackBy: trackById" |
||||
|
> |
||||
|
{%{{{ item.name }}}%} |
||||
|
</div> |
||||
|
`, |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
list: Item[]; |
||||
|
|
||||
|
trackById = trackBy<Item>('id'); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
### How to Track by a Deeply Nested Key |
||||
|
|
||||
|
You can use `byDeep` to get a `TrackByFunction` that tracks the iterated object based on a deeply nested key. For type support, you may pass in the type of the iterated item to it. |
||||
|
|
||||
|
```html |
||||
|
<!-- template of DemoComponent --> |
||||
|
|
||||
|
<div |
||||
|
*ngFor="let item of list; trackBy: track.byDeep('tenant', 'account', 'id')" |
||||
|
> |
||||
|
{%{{{ item.tenant.name }}}%} |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
`byDeep` is exported as a stand-alone function and is named `trackByDeep`. |
||||
|
|
||||
|
```js |
||||
|
import { trackByDeep } from "@abp/ng.core"; |
||||
|
|
||||
|
@Component({ |
||||
|
template: ` |
||||
|
<div |
||||
|
*ngFor="let item of list; trackBy: trackByTenantAccountId" |
||||
|
> |
||||
|
{%{{{ item.name }}}%} |
||||
|
</div> |
||||
|
`, |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
list: Item[]; |
||||
|
|
||||
|
trackByTenantAccountId = trackByDeep<Item>('tenant', 'account', 'id'); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
@ -0,0 +1,3 @@ |
|||||
|
# Layout Hooks |
||||
|
|
||||
|
TODO |
||||
@ -0,0 +1,3 @@ |
|||||
|
# ABP Datatables.Net Integration for ASP.NET Core UI |
||||
|
|
||||
|
TODO |
||||
@ -0,0 +1,82 @@ |
|||||
|
# Alerts |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-alert` is a main element to create an alert. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-alert alert-type="Primary"> |
||||
|
A simple primary alert—check it out! |
||||
|
</abp-alert> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [alerts demo page](https://bootstrap-taghelpers.abp.io/Components/Alerts) to see it in action. |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### alert-type |
||||
|
|
||||
|
A value indicates the type of the alert. Should be one of the following values: |
||||
|
|
||||
|
* `Default` (default value) |
||||
|
* `Primary` |
||||
|
* `Secondary` |
||||
|
* `Success` |
||||
|
* `Danger` |
||||
|
* `Warning` |
||||
|
* `Info` |
||||
|
* `Light` |
||||
|
* `Dark` |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-alert alert-type="Warning"> |
||||
|
A simple warning alert—check it out! |
||||
|
</abp-alert> |
||||
|
```` |
||||
|
|
||||
|
### alert-link |
||||
|
|
||||
|
A value provides matching colored links within any alert. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-alert alert-type="Danger"> |
||||
|
A simple danger alert with <a abp-alert-link href="#">an example link</a>. Give it a click if you like. |
||||
|
</abp-alert> |
||||
|
```` |
||||
|
|
||||
|
### dismissible |
||||
|
|
||||
|
A value to make the alert dismissible. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-alert alert-type="Warning" dismissible="true"> |
||||
|
Holy guacamole! You should check in on some of those fields below. |
||||
|
</abp-alert> |
||||
|
```` |
||||
|
|
||||
|
### Additional content |
||||
|
|
||||
|
`abp-alert` can also contain additional HTML elements like headings, paragraphs and dividers. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-alert alert-type="Success"> |
||||
|
<h4>Well done!</h4> |
||||
|
<p>Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.</p> |
||||
|
<hr> |
||||
|
<p class="mb-0">Whenever you need to, be sure to use margin utilities to keep things nice and tidy.</p> |
||||
|
</abp-alert> |
||||
|
```` |
||||
@ -0,0 +1,188 @@ |
|||||
|
# Cards |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-card` is a content container derived from bootstrap card element. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-card style="width: 18rem;"> |
||||
|
<img abp-card-image="Top" src="~/imgs/demo/300x200.png"/> |
||||
|
<abp-card-body> |
||||
|
<abp-card-title>Card Title</abp-card-title> |
||||
|
<abp-card-text>Some quick example text to build on the card title and make up the bulk of the card's content.</abp-card-text> |
||||
|
<a abp-button="Primary" href="#"> Go somewhere</a> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
##### Using Titles, Text and Links: |
||||
|
|
||||
|
Following tags can be used under main `abp-card` tag |
||||
|
|
||||
|
* `abp-card-title` |
||||
|
* `abp-card-subtitle` |
||||
|
* `a abp-card-link` |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
````xml |
||||
|
<abp-card style="width: 18rem;"> |
||||
|
<abp-card-body> |
||||
|
<abp-card-title>Card title</abp-card-title> |
||||
|
<abp-card-subtitle class="mb-2 text-muted">Card subtitle</abp-card-subtitle> |
||||
|
<abp-card-text>Some quick example text to build on the card title and make up the bulk of the card's content.</abp-card-text> |
||||
|
<a abp-card-link href="#">Card link</a> |
||||
|
<a abp-card-link href="#">Another link</a> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
##### Using List Groups: |
||||
|
|
||||
|
* `abp-list-group flush="true"` : `flush` attribute renders into bootstrap `list-group-flush` class which is used for removing borders and rounded corners to render list group items edge to edge in a parent container. |
||||
|
* `abp-list-group-item` |
||||
|
|
||||
|
Kitchen Sink Sample: |
||||
|
|
||||
|
````xml |
||||
|
<abp-card style="width: 18rem;"> |
||||
|
<img abp-card-image="Top" src="~/imgs/demo/300x200.png" /> |
||||
|
<abp-card-body> |
||||
|
<abp-card-title>Card Title</abp-card-title> |
||||
|
<abp-card-text>Some quick example text to build on the card title and make up the bulk of the card's content.</abp-card-text> |
||||
|
</abp-card-body> |
||||
|
<abp-list-group flush="true"> |
||||
|
<abp-list-group-item>Cras justo odio</abp-list-group-item> |
||||
|
<abp-list-group-item>Dapibus ac facilisis in</abp-list-group-item> |
||||
|
<abp-list-group-item>Vestibulum at eros</abp-list-group-item> |
||||
|
</abp-list-group> |
||||
|
<abp-card-body> |
||||
|
<a abp-card-link href="#">Card link</a> |
||||
|
<a abp-card-link href="#">Another link</a> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
##### Using Header, Footer and Blockquote: |
||||
|
|
||||
|
* `abp-card-header` |
||||
|
* `abp-card-footer` |
||||
|
* `abp-blockquote` |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-card style="width: 18rem;"> |
||||
|
<abp-card-header>Featured</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<abp-card-title> Special title treatment</abp-card-title> |
||||
|
<abp-card-text>With supporting text below as a natural lead-in to additional content.</abp-card-text> |
||||
|
<a abp-button="Primary" href="#"> Go somewhere</a> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
``` |
||||
|
|
||||
|
Quote Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-card> |
||||
|
<abp-card-header>Quote</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<abp-blockquote> |
||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.</p> |
||||
|
<footer>Someone famous in Source Title</footer> |
||||
|
</abp-blockquote> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
``` |
||||
|
|
||||
|
Footer Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-card class="text-center"> |
||||
|
<abp-card-header>Featured</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<abp-blockquote> |
||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.</p> |
||||
|
<footer>Someone famous in Source Title</footer> |
||||
|
</abp-blockquote> |
||||
|
</abp-card-body> |
||||
|
<abp-card-footer class="text-muted"> 2 days ago</abp-card-footer> |
||||
|
</abp-card> |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [cards demo page](https://bootstrap-taghelpers.abp.io/Components/Cards) to see it in action. |
||||
|
|
||||
|
## abp-card Attributes |
||||
|
|
||||
|
- **background:** A value indicates the background color of the card. |
||||
|
- **text-color**: A value indicates the color of the text inside the card. |
||||
|
- **border:** A value indicates the color of the border inside the card. |
||||
|
|
||||
|
Should be one of the following values: |
||||
|
|
||||
|
* `Default` (default value) |
||||
|
* `Primary` |
||||
|
* `Secondary` |
||||
|
* `Success` |
||||
|
* `Danger` |
||||
|
* `Warning` |
||||
|
* `Info` |
||||
|
* `Light` |
||||
|
* `Dark` |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-card background="Success" text-color="Danger" border="Dark"> |
||||
|
```` |
||||
|
|
||||
|
### sizing |
||||
|
|
||||
|
Cards has default 100% with and can be changed with custom CSS, grid classes, grid Sass mixins or [utilities](https://getbootstrap.com/docs/4.0/utilities/sizing/). |
||||
|
|
||||
|
````xml |
||||
|
<abp-card style="width: 18rem;"> |
||||
|
```` |
||||
|
|
||||
|
### card-deck and card-columns |
||||
|
|
||||
|
`abp-card` can be used inside `card-deck` or `card-columns` aswell. |
||||
|
|
||||
|
````xml |
||||
|
<div class="card-deck"> |
||||
|
<abp-card background="Primary"> |
||||
|
<abp-card-header>First Deck</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<abp-card-title> Ace </abp-card-title> |
||||
|
<abp-card-text>Here is the content for Ace.</abp-card-text> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
<abp-card background="Info"> |
||||
|
<abp-card-header>Second Deck</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<abp-card-title> Beta </abp-card-title> |
||||
|
<abp-card-text>Beta content.</abp-card-text> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
<abp-card background="Warning"> |
||||
|
<abp-card-header>Third Deck</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<abp-card-title> Epsilon </abp-card-title> |
||||
|
<abp-card-text>Content for Epsilon.</abp-card-text> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
</div> |
||||
|
```` |
||||
@ -0,0 +1,92 @@ |
|||||
|
# Collapse |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-collapse-body` is the main container for showing and hiding content. `abp-collapse-id` is used to show and hide the content container. Can be triggered with both `abp-button` and `a` tags. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-button button-type="Primary" abp-collapse-id="collapseExample" text="Button with data-target" /> |
||||
|
<a abp-button="Primary" abp-collapse-id="collapseExample"> Link with href </a> |
||||
|
|
||||
|
<abp-collapse-body id="collapseExample"> |
||||
|
Anim pariatur wolf moon tempor,,, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. |
||||
|
</abp-collapse-body> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [collapse demo page](https://bootstrap-taghelpers.abp.io/Components/Collapse) to see it in action. |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### show |
||||
|
|
||||
|
A value indicates if the collapse body will be initialized visible or hidden. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
### multi |
||||
|
|
||||
|
A value indicates if an `abp-collapse-body` can be shown or hidden by an element that can show/hide multiple collapse bodies. Basically, this attribute adds "multi-collapse" class to `abp-collapse-body`. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
````xml |
||||
|
<a abp-button="Primary" abp-collapse-id="FirstCollapseExample"> Toggle first element </a> |
||||
|
<abp-button button-type="Primary" abp-collapse-id="SecondCollapseExample" text="Toggle second element" /> |
||||
|
<abp-button button-type="Primary" abp-collapse-id="FirstCollapseExample SecondCollapseExample" text="Toggle both elements" /> |
||||
|
|
||||
|
<abp-row class="mt-3"> |
||||
|
<abp-column size-sm="_6"> |
||||
|
<abp-collapse-body id="FirstCollapseExample" multi="true"> |
||||
|
Curabitur porta porttitor libero eu luctus. Praesent ultrices mattis commodo. Integer sodales massa risus, in molestie enim sagittis blandit |
||||
|
</abp-collapse-body> |
||||
|
</abp-column> |
||||
|
<abp-column size-sm="_6"> |
||||
|
<abp-collapse-body id="SecondCollapseExample" multi="true"> |
||||
|
Anim pariatur wolf moon tempor,,, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. |
||||
|
</abp-collapse-body> |
||||
|
</abp-column> |
||||
|
</abp-row> |
||||
|
```` |
||||
|
|
||||
|
## Accordion example |
||||
|
|
||||
|
`abp-accordion` is the main container for the accordion items. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-accordion> |
||||
|
<abp-accordion-item title="Collapsible Group Item #1"> |
||||
|
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry rtat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. |
||||
|
</abp-accordion-item> |
||||
|
<abp-accordion-item title="Collapsible Group Item #2"> |
||||
|
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. |
||||
|
</abp-accordion-item> |
||||
|
<abp-accordion-item title="Collapsible Group Item #3"> |
||||
|
Anim pariatur wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. |
||||
|
</abp-accordion-item> |
||||
|
</abp-accordion> |
||||
|
```` |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### active |
||||
|
|
||||
|
A value indicates if the accordion item will be initialized visible or hidden. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
### title |
||||
|
|
||||
|
A value indicates the visible title of the accordion item. Should be a string value. |
||||
@ -0,0 +1,97 @@ |
|||||
|
# Dropdowns |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-dropdown` is the main container for dropdown content. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-dropdown> |
||||
|
<abp-dropdown-button text="Dropdown button" /> |
||||
|
<abp-dropdown-menu> |
||||
|
<abp-dropdown-item href="#">Action</abp-dropdown-item> |
||||
|
<abp-dropdown-item href="#">Another action</abp-dropdown-item> |
||||
|
<abp-dropdown-item href="#">Something else here</abp-dropdown-item> |
||||
|
</abp-dropdown-menu> |
||||
|
</abp-dropdown> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [dropdown demo page](https://bootstrap-taghelpers.abp.io/Components/Dropdowns) to see it in action. |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### direction |
||||
|
|
||||
|
A value indicates which direction the dropdown buttons will be displayed to. Should be one of the following values: |
||||
|
|
||||
|
* `Down` (default value) |
||||
|
* `Up` |
||||
|
* `Right` |
||||
|
* `Left` |
||||
|
|
||||
|
### dropdown-style |
||||
|
|
||||
|
A value indicates if an `abp-dropdown-button` will have split icon for dropdown. Should be one of the following values: |
||||
|
|
||||
|
* `Single` (default value) |
||||
|
* `Split` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Menu items |
||||
|
|
||||
|
`abp-dropdown-menu` is the main container for dropdown menu items. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-dropdown> |
||||
|
<abp-dropdown-button button-type="Secondary" text="Dropdown"/> |
||||
|
<abp-dropdown-menu> |
||||
|
<abp-dropdown-header>Dropdown Header</abp-dropdown-header> |
||||
|
<abp-dropdown-item href="#">Action</abp-dropdown-item> |
||||
|
<abp-dropdown-item active="true" href="#">Active action</abp-dropdown-item> |
||||
|
<abp-dropdown-item disabled="true" href="#">Disabled action</abp-dropdown-item> |
||||
|
<abp-dropdown-divider/> |
||||
|
<abp-dropdown-item-text>Dropdown Item Text</abp-dropdown-item-text> |
||||
|
<abp-dropdown-item href="#">Something else here</abp-dropdown-item> |
||||
|
</abp-dropdown-menu> |
||||
|
</abp-dropdown> |
||||
|
```` |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### align |
||||
|
|
||||
|
A value indicates which direction `abp-dropdown-menu` items will be aligned to. Should be one of the following values: |
||||
|
|
||||
|
* `Left` (default value) |
||||
|
* `Right` |
||||
|
|
||||
|
### Additional content |
||||
|
|
||||
|
`abp-dropdown-menu` can also contain additional HTML elements like headings, paragraphs, dividers or form element. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-dropdown > |
||||
|
<abp-dropdown-button button-type="Secondary" text="Dropdown With Form"/> |
||||
|
<abp-dropdown-menu> |
||||
|
<form class="px-4 py-3"> |
||||
|
<abp-input asp-for="EmailAddress"></abp-input> |
||||
|
<abp-input asp-for="Password"></abp-input> |
||||
|
<abp-input asp-for="RememberMe"></abp-input> |
||||
|
<abp-button button-type="Primary" text="Sign In" type="submit" /> |
||||
|
</form> |
||||
|
<abp-dropdown-divider></abp-dropdown-divider> |
||||
|
<abp-dropdown-item href="#">New around here? Sign up</abp-dropdown-item> |
||||
|
<abp-dropdown-item href="#">Forgot password?</abp-dropdown-item> |
||||
|
</abp-dropdown-menu> |
||||
|
</abp-dropdown> |
||||
|
```` |
||||
@ -0,0 +1,286 @@ |
|||||
|
# Grids |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
Abp tag helpers for bootstrap based grid system. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [grids demo page](https://bootstrap-taghelpers.abp.io/Components/Grids) to see it in action. |
||||
|
|
||||
|
|
||||
|
|
||||
|
### Sizing |
||||
|
|
||||
|
**Equal Width:** Creates columns with equal width. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
````xml |
||||
|
<abp-container> |
||||
|
<abp-row> |
||||
|
<abp-column abp-border="Info">1 of 2</abp-column> |
||||
|
<abp-column abp-border="Danger">2 of 2</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-column abp-border="Primary">1 of 3</abp-column> |
||||
|
<abp-column abp-border="Secondary">2 of 3</abp-column> |
||||
|
<abp-column abp-border="Dark">3 of 3</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-container> |
||||
|
```` |
||||
|
|
||||
|
**Column Breaker:** `abp-column-breaker` is used for breaking the automatic width of placement of the current row and starting in a new row afterwards. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
````xml |
||||
|
<abp-container> |
||||
|
<abp-row> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column-breaker/> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column>column</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-container> |
||||
|
```` |
||||
|
|
||||
|
**Setting one column width:** size attribute is used for setting the width for a specific column. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-container> |
||||
|
<abp-row> |
||||
|
<abp-column>1 of 3</abp-column> |
||||
|
<abp-column size="_6">2 of 3 (wider)</abp-column> |
||||
|
<abp-column>3 of 3</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-column>1 of 3</abp-column> |
||||
|
<abp-column size="_5">2 of 3 (wider)</abp-column> |
||||
|
<abp-column>3 of 3</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-container> |
||||
|
``` |
||||
|
|
||||
|
**Variable width content:** Auto resizing column based on content. |
||||
|
|
||||
|
```xml |
||||
|
<abp-container> |
||||
|
<abp-row h-align="Center"> |
||||
|
<abp-column size-lg="_2" abp-border="Info">1 of 3</abp-column> |
||||
|
<abp-column size-md="Auto" abp-border="Danger">Contrary to popular belief, Lorem Ipsum is not simply random text.</abp-column> |
||||
|
<abp-column size-lg="_2" abp-border="Warning">3 of 3</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-column>1 of 3</abp-column> |
||||
|
<abp-column size-md="Auto">Variable width content</abp-column> |
||||
|
<abp-column size-lg="_2">3 of 3</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-container> |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
### Responsive Classes |
||||
|
|
||||
|
Responsive classes can be used strongly typed within abp tags. |
||||
|
|
||||
|
```xml |
||||
|
<abp-row> |
||||
|
<abp-column size-sm="_8">col-sm-8</abp-column> |
||||
|
<abp-column size-sm="_4">col-sm-4</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-column size-sm="_">col-sm</abp-column> |
||||
|
<abp-column size-sm="_">col-sm</abp-column> |
||||
|
<abp-column size-sm="_">col-sm</abp-column> |
||||
|
<abp-column size-sm="_">col-sm</abp-column> |
||||
|
</abp-row> |
||||
|
<!-- Stack the columns on mobile by making one full-width and the other half-width --> |
||||
|
<abp-row> |
||||
|
<abp-column size="_12" size-md="_8">.col-12 .col-md-8</abp-column> |
||||
|
<abp-column size="_6" size-md="_4">.col-6 .col-md-4</abp-column> |
||||
|
</abp-row> |
||||
|
|
||||
|
<!-- Columns start at 50% wide on mobile and bump up to 33.3% wide on desktop --> |
||||
|
<abp-row> |
||||
|
<abp-column size="_6" size-md="_4">.col-6 .col-md-4</abp-column> |
||||
|
<abp-column size="_6" size-md="_4">.col-6 .col-md-4</abp-column> |
||||
|
<abp-column size="_6" size-md="_4">.col-6 .col-md-4</abp-column> |
||||
|
</abp-row> |
||||
|
|
||||
|
<!-- Columns are always 50% wide, on mobile and desktop --> |
||||
|
<abp-row> |
||||
|
<abp-column size="_6">.col-6</abp-column> |
||||
|
<abp-column size="_6">.col-6</abp-column> |
||||
|
</abp-row> |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
### Alignment |
||||
|
|
||||
|
Column alignments can be done strongly typed in abp tags with both vertically and horizontally. |
||||
|
|
||||
|
**Vertical-alignment**: `v-align` attribute value is used to align the columns vertically. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-container> |
||||
|
<abp-row v-align="Start"> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column>column</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row v-align="Center"> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column>column</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row v-align="End"> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column>column</abp-column> |
||||
|
<abp-column>column</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-container> |
||||
|
``` |
||||
|
|
||||
|
**Horizontal-alignment**: `h-align` attribute value is used to align the columns horizontally. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-container> |
||||
|
<abp-row h-align="Start"> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row h-align="Center"> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row h-align="End"> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row h-align="Around"> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row h-align="Between"> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-container> |
||||
|
``` |
||||
|
|
||||
|
**No gutters**: The gutters between columns in predefined grid classes can be removed with `gutters="false"`. This removes the negative `margin`s from `abp-row` and the horizontal `padding` from all immediate children columns. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-row gutters="false"> |
||||
|
<abp-column size="_8">One of two columns</abp-column> |
||||
|
<abp-column size="_4">One of two columns</abp-column> |
||||
|
</abp-row> |
||||
|
``` |
||||
|
|
||||
|
**Column wrapping**: If more than 12 columns are placed within a single row, each group of extra columns will, as one unit, wrap onto a new line. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-row> |
||||
|
<abp-column size="_9">.col-9</abp-column> |
||||
|
<abp-column size="_4">.col-4<br>Since 9 + 4 = 13 > 12, this 4-column-wide div gets wrapped onto a new line as one contiguous unit.</abp-column> |
||||
|
<abp-column size="_6">.col-6<br>Subsequent columns continue along the new line.s</abp-column> |
||||
|
</abp-row> |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
### Reordering |
||||
|
|
||||
|
**Order Classes**: `order` attribute is used for controlling the visual order of the content. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-container> |
||||
|
<abp-row> |
||||
|
<abp-column order="_12">First, but Last</abp-column> |
||||
|
<abp-column>Second, but unordered</abp-column> |
||||
|
<abp-column order="_6">Third, but Second</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-container> |
||||
|
``` |
||||
|
|
||||
|
**Offsetting columns**: `offset` attribute is used for setting the offset of the grid columns. |
||||
|
|
||||
|
Sample: |
||||
|
|
||||
|
```xml |
||||
|
<abp-container> |
||||
|
<abp-row> |
||||
|
<abp-column size-md="_4">.col-md-4</abp-column> |
||||
|
<abp-column size-md="_4" offset-md="_4">.col-md-4 .offset-md-4</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-column size-md="_3" offset-md="_3">.col-md-3 .offset-md-3</abp-column> |
||||
|
<abp-column size-md="_3" offset-md="_3">.col-md-3 .offset-md-3</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-column size-md="_6" offset-md="_3">.col-md-6 .offset-md-3</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-column size-sm="_5" size-md="_6">.col-sm-5 .col-md-6</abp-column> |
||||
|
<abp-column size-sm="_5" offset-sm="_2" size-md="_6" offset-md="_">.col-sm-5 .offset-sm-2 .col-md-6 .offset-md-0</abp-column> |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-column size-sm="_6" size-md="_5" size-lg="_6">col-sm-6 .col-md-5 .col-lg-6</abp-column> |
||||
|
<abp-column size-sm="_6" size-md="_5" offset-md="_2" size-lg="_6" offset-lg="_">.col-sm-6 .col-md-5 .offset-md-2 .col-lg-6 .offset-lg-0</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-container> |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## abp-row Attributes |
||||
|
|
||||
|
- **v-align:** A value indicates the vertical positioning of the containing columns. Should be one of the following values: |
||||
|
* `Default` (default value) |
||||
|
* `Start` |
||||
|
* `Center` |
||||
|
* `End` |
||||
|
|
||||
|
- **h-align**: A value indicates the horizontal positioning of the containing columns. Should be one of the following values: |
||||
|
* `Default` (default value) |
||||
|
* `Start` |
||||
|
* `Center` |
||||
|
* `Around` |
||||
|
* `Between` |
||||
|
* `End` |
||||
|
- **gutter**: A value indicates if the negative `margin` and horizontal `padding` will be removed from all children columns. Will act as `true` value if this attribute is not set. Should be one of the following values: |
||||
|
* `true` |
||||
|
* `false` |
||||
|
|
||||
|
## abp-column Attributes |
||||
|
|
||||
|
- **size:** A value indicates the width of the column from `_`, `Undefined`, `_1`..`_12`, `Auto`. Or can be used with predefined values like: |
||||
|
- `size-sm` |
||||
|
- `size-md` |
||||
|
- `size-lg` |
||||
|
- `size-xl` |
||||
|
- **order**: A value indicates the order of column from `Undefined`, `_1`..`_12`, `First` and `Last`. |
||||
|
- **offset:** A value indicates offset of the column from `_`, `Undefined`, `_1`..`_12`, `Auto`. Or can be used with predefined values like: |
||||
|
- `offset-sm` |
||||
|
- `offset-md` |
||||
|
- `offset-lg` |
||||
|
- `offset-xl` |
||||
|
|
||||
@ -0,0 +1,78 @@ |
|||||
|
# List Groups |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-list-group` is the main container for list group content. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-list-group> |
||||
|
<abp-list-group-item>Cras justo odio</abp-list-group-item> |
||||
|
<abp-list-group-item>Dapibus ac facilisis in</abp-list-group-item> |
||||
|
<abp-list-group-item>Morbi leo risus</abp-list-group-item> |
||||
|
<abp-list-group-item>Vestibulum at eros</abp-list-group-item> |
||||
|
</abp-list-group> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [list groups demo page](https://bootstrap-taghelpers.abp.io/Components/ListGroups) to see it in action. |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### flush |
||||
|
|
||||
|
A value indicates `abp-list-group` items to remove some borders and rounded corners to render list group items edge-to-edge in a parent container. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
### active |
||||
|
|
||||
|
A value indicates if an `abp-list-group-item` to be active. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
### disabled |
||||
|
|
||||
|
A value indicates if an `abp-list-group-item` to be disabled. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
### href |
||||
|
|
||||
|
A value indicates if an `abp-list-group-item` has a link. Should be a string link value. |
||||
|
|
||||
|
### type |
||||
|
|
||||
|
A value indicates an `abp-list-group-item` style class with a stateful background and color. Should be one of the following values: |
||||
|
|
||||
|
* `Default` (default value) |
||||
|
* `Primary` |
||||
|
* `Secondary` |
||||
|
* `Success` |
||||
|
* `Danger` |
||||
|
* `Warning` |
||||
|
* `Info` |
||||
|
* `Light` |
||||
|
* `Dark` |
||||
|
* `Link` |
||||
|
|
||||
|
### Additional content |
||||
|
|
||||
|
`abp-list-group-item` can also contain additional HTML elements like spans. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-list-group> |
||||
|
<abp-list-group-item>Cras justo odio <span abp-badge-pill="Primary">14</span></abp-list-group-item> |
||||
|
<abp-list-group-item>Dapibus ac facilisis in <span abp-badge-pill="Primary">2</span></abp-list-group-item> |
||||
|
<abp-list-group-item>Morbi leo risus <span abp-badge-pill="Primary">1</span></abp-list-group-item> |
||||
|
</abp-list-group> |
||||
|
```` |
||||
@ -0,0 +1,81 @@ |
|||||
|
# Modals |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-modal` is a main element to create a modal. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-button button-type="Primary" data-toggle="modal" data-target="#myModal">Launch modal</abp-button> |
||||
|
|
||||
|
<abp-modal centered="true" size="Large" id="myModal"> |
||||
|
<abp-modal-header title="Modal title"></abp-modal-header> |
||||
|
<abp-modal-body> |
||||
|
Woohoo, you're reading this text in a modal! |
||||
|
</abp-modal-body> |
||||
|
<abp-modal-footer buttons="Close"></abp-modal-footer> |
||||
|
</abp-modal> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [modals demo page](https://bootstrap-taghelpers.abp.io/Components/Modals) to see it in action. |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### centered |
||||
|
|
||||
|
A value indicates the positioning of the modal. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
### size |
||||
|
|
||||
|
A value indicates the size of the modal. Should be one of the following values: |
||||
|
|
||||
|
* `Default` (default value) |
||||
|
* `Small` |
||||
|
* `Large` |
||||
|
* `ExtraLarge` |
||||
|
|
||||
|
### static |
||||
|
|
||||
|
A value indicates if the modal will be static. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
### Additional content |
||||
|
|
||||
|
`abp-modal-footer` can have multiple buttons with alignment option. |
||||
|
|
||||
|
Add `@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal` to your page. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-button button-type="Primary" data-toggle="modal" data-target="#myModal">Launch modal</abp-button> |
||||
|
|
||||
|
<abp-modal centered="true" size="Large" id="myModal" static="true"> |
||||
|
<abp-modal-header title="Modal title"></abp-modal-header> |
||||
|
<abp-modal-body> |
||||
|
Woohoo, you're reading this text in a modal! |
||||
|
</abp-modal-body> |
||||
|
<abp-modal-footer buttons="@(AbpModalButtons.Save|AbpModalButtons.Close)" button-alignment="Between"></abp-modal-footer> |
||||
|
</abp-modal> |
||||
|
```` |
||||
|
|
||||
|
### button-alignment |
||||
|
|
||||
|
A value indicates the positioning of your modal footer buttons. Should be one of the following values: |
||||
|
|
||||
|
* `Default` (default value) |
||||
|
* `Start` |
||||
|
* `Center` |
||||
|
* `Around` |
||||
|
* `Between` |
||||
|
* `End` |
||||
@ -0,0 +1,57 @@ |
|||||
|
# Paginator |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-paginator` is the abp tag for pagination. Requires `Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination.PagerModel` type of model. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-paginator model="Model.PagerModel" show-info="true"></abp-paginator> |
||||
|
```` |
||||
|
|
||||
|
Model: |
||||
|
|
||||
|
````xml |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components |
||||
|
{ |
||||
|
public class PaginatorModel : PageModel |
||||
|
{ |
||||
|
public PagerModel PagerModel { get; set; } |
||||
|
|
||||
|
public void OnGet(int currentPage, string sort) |
||||
|
{ |
||||
|
PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [paginator demo page](https://bootstrap-taghelpers.abp.io/Components/Paginator) to see it in action. |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### model |
||||
|
|
||||
|
`Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination.PagerModel` type of model can be initialized with the following data: |
||||
|
|
||||
|
* `totalCount` |
||||
|
* `shownItemsCount` |
||||
|
* `currentPage` |
||||
|
* `pageSize` |
||||
|
* `pageUrl` |
||||
|
* `sort` (default null) |
||||
|
|
||||
|
### show-info |
||||
|
|
||||
|
A value indicates if an extra information about start, end and total records will be displayed. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
@ -0,0 +1,70 @@ |
|||||
|
# Progress Bars |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-progress-bar` is the abp tag for progress bar status. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-progress-bar value="70" /> |
||||
|
|
||||
|
<abp-progress-bar type="Warning" value="25"> %25 </abp-progress-bar> |
||||
|
|
||||
|
<abp-progress-bar type="Success" value="40" strip="true"/> |
||||
|
|
||||
|
<abp-progress-bar type="Dark" value="10" min-value="5" max-value="15" strip="true"> %50 </abp-progress-bar> |
||||
|
|
||||
|
<abp-progress-group> |
||||
|
<abp-progress-part type="Success" value="25"/> |
||||
|
<abp-progress-part type="Danger" value="10" strip="true"> %10 </abp-progress-part> |
||||
|
<abp-progress-part type="Primary" value="50" animation="true" strip="true" /> |
||||
|
</abp-progress-group> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [progress bars demo page](https://bootstrap-taghelpers.abp.io/Components/Progress-Bars) to see it in action. |
||||
|
|
||||
|
## Attributes |
||||
|
|
||||
|
### value |
||||
|
|
||||
|
A value indicates the current progress of the bar. |
||||
|
|
||||
|
### type |
||||
|
|
||||
|
A value indicates the background color of the progress bar. Should be one of the following values: |
||||
|
|
||||
|
* `Default` (default value) |
||||
|
* `Secondary` |
||||
|
* `Success` |
||||
|
* `Danger` |
||||
|
* `Warning` |
||||
|
* `Info` |
||||
|
* `Light` |
||||
|
* `Dark` |
||||
|
|
||||
|
### min-value |
||||
|
|
||||
|
Minimum value of the progress bar. Default is 0. |
||||
|
|
||||
|
### max-value |
||||
|
|
||||
|
Maximum value of the progress bar. Default is 100. |
||||
|
|
||||
|
### strip |
||||
|
|
||||
|
A value indicates if the background style of the progress bar is stripped. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
|
|
||||
|
### animation |
||||
|
|
||||
|
A value indicates if the stripped background style of the progress bar is animated. Should be one of the following values: |
||||
|
|
||||
|
* `false` (default value) |
||||
|
* `true` |
||||
@ -0,0 +1,93 @@ |
|||||
|
# Tabs |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-tab` is the basic tab navigation content container derived from bootstrap tab element. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-tabs> |
||||
|
<abp-tab title="Home"> |
||||
|
Content_Home |
||||
|
</abp-tab> |
||||
|
<abp-tab-link title="Link" href="#" /> |
||||
|
<abp-tab title="profile"> |
||||
|
Content_Profile |
||||
|
</abp-tab> |
||||
|
<abp-tab-dropdown title="Contact" name="ContactDropdown"> |
||||
|
<abp-tab title="Contact 1" parent-dropdown-name="ContactDropdown"> |
||||
|
Content_1_Content |
||||
|
</abp-tab> |
||||
|
<abp-tab title="Contact 2" parent-dropdown-name="ContactDropdown"> |
||||
|
Content_2_Content |
||||
|
</abp-tab> |
||||
|
</abp-tab-dropdown> |
||||
|
</abp-tabs> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [cards demo page](https://bootstrap-taghelpers.abp.io/Components/Cards) to see it in action. |
||||
|
|
||||
|
## abp-tab Attributes |
||||
|
|
||||
|
- **title**: Sets the text of the tab menu. |
||||
|
- **name:** Sets "id" attribute of generated elements. Default value is a Guid. Not needed unless tabs are changed or modified with Jquery. |
||||
|
- **active**: Sets the active tab. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-tabs name="TabId"> |
||||
|
<abp-tab name="nav-home" title="Home"> |
||||
|
Content_Home |
||||
|
</abp-tab> |
||||
|
<abp-tab name="nav-profile" active="true" title="profile"> |
||||
|
Content_Profile |
||||
|
</abp-tab> |
||||
|
<abp-tab name="nav-contact" title="Contact"> |
||||
|
Content_Contact |
||||
|
</abp-tab> |
||||
|
</abp-tabs> |
||||
|
```` |
||||
|
|
||||
|
### Pills |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-tabs tab-style="Pill"> |
||||
|
<abp-tab title="Home"> |
||||
|
Content_Home |
||||
|
</abp-tab> |
||||
|
<abp-tab title="profile"> |
||||
|
Content_Profile |
||||
|
</abp-tab> |
||||
|
<abp-tab title="Contact"> |
||||
|
Content_Contact |
||||
|
</abp-tab> |
||||
|
</abp-tabs> |
||||
|
```` |
||||
|
|
||||
|
### Vertical |
||||
|
|
||||
|
**vertical-header-size**: Sets the column width of tab headers. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````xml |
||||
|
<abp-tabs tab-style="PillVertical" vertical-header-size="_2" > |
||||
|
<abp-tab active="true" title="Home"> |
||||
|
Content_Home |
||||
|
</abp-tab> |
||||
|
<abp-tab title="profile"> |
||||
|
Content_Profile |
||||
|
</abp-tab> |
||||
|
<abp-tab title="Contact"> |
||||
|
Content_Contact |
||||
|
</abp-tab> |
||||
|
</abp-tabs> |
||||
|
```` |
||||
@ -0,0 +1,35 @@ |
|||||
|
# Tooltips |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
`abp-tooltip` is the abp tag for tooltips. |
||||
|
|
||||
|
Basic usage: |
||||
|
|
||||
|
````xml |
||||
|
<abp-button abp-tooltip="Tooltip"> |
||||
|
Tooltip Default |
||||
|
</abp-button> |
||||
|
|
||||
|
<abp-button abp-tooltip-top="Tooltip"> |
||||
|
Tooltip on top |
||||
|
</abp-button> |
||||
|
|
||||
|
<abp-button abp-tooltip-right="Tooltip"> |
||||
|
Tooltip on right |
||||
|
</abp-button> |
||||
|
|
||||
|
<abp-button abp-tooltip-bottom="Tooltip"> |
||||
|
Tooltip on bottom |
||||
|
</abp-button> |
||||
|
|
||||
|
<abp-button disabled="true" abp-tooltip="Tooltip"> |
||||
|
Disabled button Tooltip |
||||
|
</abp-button> |
||||
|
```` |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
See the [tooltips demo page](https://bootstrap-taghelpers.abp.io/Components/Tooltips) to see it in action. |
||||
@ -0,0 +1,3 @@ |
|||||
|
# Toolbars |
||||
|
|
||||
|
TODO |
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 21 KiB |
@ -0,0 +1,3 @@ |
|||||
|
# ASP.NET Boilerplate v5+ 迁移到 ABP Framework |
||||
|
|
||||
|
TODO... |
||||
@ -1,3 +1,138 @@ |
|||||
# 后台工作者 |
# 后台工作者 |
||||
|
|
||||
TODO |
## 介绍 |
||||
|
|
||||
|
背景工人在应用简单独立的线程在后台运行。一般来说,他们定期运行,以执行一些任务。例子; |
||||
|
后台工作者在应用程序后台运行的简单的独立线程,一般来说它们定期运行执行一些任务.例如; |
||||
|
|
||||
|
* 后台工作者可以定期**删除过时的日志**. |
||||
|
* 后台工作者可以定期检查**不活跃的用户**并且向其**发送邮件**使用户继续使用你的应用程序. |
||||
|
|
||||
|
## 创建一个后台工作者 |
||||
|
|
||||
|
后台工作者应该直接或间接的继承 `IBackgroundWorker` 接口. |
||||
|
|
||||
|
> 后台工作者是[单例](Dependency-Injection.md)的. 所以实例化运行你的工作者类的单个实例. |
||||
|
|
||||
|
### BackgroundWorkerBase |
||||
|
|
||||
|
`BackgroundWorkerBase` 是创建后台工作者的简单方法. |
||||
|
|
||||
|
````csharp |
||||
|
public class MyWorker : BackgroundWorkerBase |
||||
|
{ |
||||
|
public override Task StartAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
|
||||
|
public override Task StopAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`StartAsync` 开始你的工作者(在应用程序启动时),`StopAsync` 停止它(在应用程序关闭时). |
||||
|
|
||||
|
> 你可以直接实现 `IBackgroundWorker`, 但 `BackgroundWorkerBase` 提供了一些像 `Logger` 的常用属性. |
||||
|
|
||||
|
### AsyncPeriodicBackgroundWorkerBase |
||||
|
|
||||
|
假设我们要设置用户为不活跃用户(如果用户最近30天未登录应用程序).`AsyncPeriodicBackgroundWorkerBase` 类简化了创建定期工作者的过程,我们在下面的示例中使用它: |
||||
|
|
||||
|
````csharp |
||||
|
public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase |
||||
|
{ |
||||
|
public PassiveUserCheckerWorker( |
||||
|
AbpTimer timer, |
||||
|
IServiceScopeFactory serviceScopeFactory |
||||
|
) : base( |
||||
|
timer, |
||||
|
serviceScopeFactory) |
||||
|
{ |
||||
|
Timer.Period = 600000; //10 minutes |
||||
|
} |
||||
|
|
||||
|
protected override async Task DoWorkAsync( |
||||
|
PeriodicBackgroundWorkerContext workerContext) |
||||
|
{ |
||||
|
Logger.LogInformation("Starting: Setting status of inactive users..."); |
||||
|
|
||||
|
//Resolve dependencies |
||||
|
var userRepository = workerContext |
||||
|
.ServiceProvider |
||||
|
.GetRequiredService<IUserRepository>(); |
||||
|
|
||||
|
//Do the work |
||||
|
await userRepository.UpdateInactiveUserStatusesAsync(); |
||||
|
|
||||
|
Logger.LogInformation("Completed: Setting status of inactive users..."); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性。 |
||||
|
* 它需要实现 `DoWorkAsync` 方法**执行**定期任务. |
||||
|
* 最好使用 `PeriodicBackgroundWorkerContext` **解析依赖** 而不是构造函数. 因为 `AsyncPeriodicBackgroundWorkerBase` 使用 `IServiceScope` 在你的任务执行结束时会对其 **disposed**. |
||||
|
* `AsyncPeriodicBackgroundWorkerBase` **捕获并记录** 由 `DoWorkAsync` 方法抛出的 **异常**. |
||||
|
|
||||
|
## 注册后台工作者 |
||||
|
|
||||
|
创建一个后台工作者后,你应该将其添加到 `IBackgroundWorkerManager`. 最常见的地方是模块类的 `OnApplicationInitialization` 方法: |
||||
|
|
||||
|
````csharp |
||||
|
[DependsOn(typeof(AbpBackgroundWorkersModule))] |
||||
|
public class MyModule : AbpModule |
||||
|
{ |
||||
|
public override void OnApplicationInitialization( |
||||
|
ApplicationInitializationContext context) |
||||
|
{ |
||||
|
context.AddBackgroundWorker<PassiveUserCheckerWorker>(); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`context.AddBackgroundWorker(...)` 是以下代码的简化扩展方法: |
||||
|
|
||||
|
````csharp |
||||
|
context.ServiceProvider |
||||
|
.GetRequiredService<IBackgroundWorkerManager>() |
||||
|
.Add( |
||||
|
context |
||||
|
.ServiceProvider |
||||
|
.GetRequiredService<PassiveUserCheckerWorker>() |
||||
|
); |
||||
|
```` |
||||
|
|
||||
|
所以,它解析了给定的后台工作者并添加到 `IBackgroundWorkerManager`. |
||||
|
|
||||
|
如果我们通常在 `OnApplicationInitialization` 添加工作者,但并不是强制的. 你可以在应用程序的任何地方注入 `IBackgroundWorkerManager` 并在运行时添加工作者. 在你的应用程序关闭时Background worker manager会释放所有已注册的后台工作者. |
||||
|
|
||||
|
## Options |
||||
|
|
||||
|
`AbpBackgroundWorkerOptions` 是用于设置后台工作者的选择. 目前只有一个选项: |
||||
|
|
||||
|
* `IsEnabled` (默认值: true): 用于为你的应用程序启动或禁用后台工作者系统. |
||||
|
|
||||
|
## 让应用程序始终运行 |
||||
|
|
||||
|
后台工作者只有在你的应用程序运行时才会工作. 如果你将后台作业托管在web应用程序中(这是默认行为),那么你应该确保你的web应用程序被配置为始终运行. 否则只有在你的应用程序正在运行时后台作业才会工作. |
||||
|
|
||||
|
## 在集群运行 |
||||
|
|
||||
|
如果你在集群环境中运行同时运行应用程序的多个实现,这种情况下要小心,每个应用程序都运行相同的后台工作者,如果你的工作者在相同的资源上运行(例如处理相同的数据),那么可能会产生冲突. |
||||
|
|
||||
|
如果这对你的工作者是一个问题,你有两个选项: |
||||
|
|
||||
|
* 使用上面提到的 `AbpBackgroundWorkerOptions` 禁用其他的后台工作者系统,只保留一个实例. |
||||
|
* 所有的应用程序都禁用后台工作者系统,创建一个特殊的应用程序在一个服务上运行执行工作者. |
||||
|
|
||||
|
## Quartz 集成 |
||||
|
|
||||
|
ABP框架的后台工作者系统可以很好的执行周期任务. 但是你可能需要使用更高级的任务调度,像[Quartz](https://www.quartz-scheduler.net/). 参阅社区贡献的[Quartz集成](Background-Workers-Quartz.md) |
||||
|
|
||||
|
## 另请参阅 |
||||
|
|
||||
|
* [后台工作者的Quartz集成](Background-Workers-Quartz.md) |
||||
|
* [后台作业](Background-Jobs.md) |
||||
@ -0,0 +1,142 @@ |
|||||
|
# ABP框架v2.3.0已经发布! |
||||
|
|
||||
|
在**新冠病毒**的日子里,我们发布了**ABP框架v2.3**, 这篇文章将说明本次发布**新增内容**和过去的两周**我们做了什么**. |
||||
|
|
||||
|
## 关于新冠病毒和我们的团队 |
||||
|
|
||||
|
关于冠状病毒的状况**我们很难过**.在[Volosoft](https://volosoft.com/)的团队,我们有不同国家的**远程工作者**在自己家里工作.从上周开始,我们已经**完全开始在家远程工作**,包括我们的主要办公室的员工. |
||||
|
|
||||
|
我们相信并祈祷人类会在很短的时间内克服这个问题. |
||||
|
|
||||
|
## 关于发布周期 |
||||
|
|
||||
|
从ABP v2.1.0开始,我们开始**每两周**的周四发布功能版本.本次是该决定后的第3次发布,我们看到这种方式目前运转良好,并提高了我们的灵活性. |
||||
|
|
||||
|
我们将继续每两周发布**功能版本**(如v2.4,v2.5).另外,如果需要我们会随时发布**热修复版本**(如v2.3.1,v2.3.2). |
||||
|
|
||||
|
## ABP框架v2.3.0新增内容 |
||||
|
|
||||
|
我们已在这两周的开发周期内通过**393次提交**完成和合并了 **[104](https://github.com/abpframework/abp/milestone/30?closed=1)个issue和pull request**. |
||||
|
|
||||
|
我将介绍这个版本加入的一些新功能和改善. |
||||
|
|
||||
|
### React Native移动应用程序 |
||||
|
|
||||
|
我们终于完成了**react native移动应用程序**.目前,它可以让你**登录**,管理**用户**和**租户**.它利用ABP框架相同的设置,授权和本地化系统. |
||||
|
|
||||
|
应用程序的一些截图: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
它没有太多的功能,但它是你的移动应用程序一个**完美的起点**,因为它是完全集成到后端并支持多租户. |
||||
|
|
||||
|
### Angular TypeScript代理生成器 |
||||
|
|
||||
|
从我们的Angular应用程序中调用服务器中的REST端点是很常见的.这种情况下,我们一般创建**服务**(在服务器上包含各个服务的方法)和**模型对象**(对应服务器上的[DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)). |
||||
|
|
||||
|
除了手动创建这样的与服务器交互的服务外,我们可以使用像[NSWAG](https://github.com/RicoSuter/NSwag)工具来为我们生成服务代理.但是NSWAG有以下几个我们遇到的问题: |
||||
|
|
||||
|
* 它产生一个**大,单一**的.ts文件; |
||||
|
* 当你的应用程序增长时,它变得**太大**了. |
||||
|
* 它不适合ABP框架的 **[模块化](https://docs.abp.io/en/abp/latest/Module-Development-Basics)方式**. |
||||
|
* 它创建了有点**丑陋的代码**.我们希望有一个干净的代码(就像我们手写的). |
||||
|
* 它不能生成服务器端声明的相同的**方法签名**(因为swagger.json不能准确地反映后端服务的方法签名).我们已创建了公开服务器端方法约定的端点,来允许客户端生成更好的客户端代理. |
||||
|
|
||||
|
因此,我们决定创建一个ABP CLI命令来自动生成typescript客户端代理([#2222](https://github.com/abpframework/abp/issues/2222)),用于在ABP框架中开发REST API. |
||||
|
|
||||
|
它用起来很简单.只需要在你Angular应用程序的**根文件夹**运行以下命令 |
||||
|
|
||||
|
````bash |
||||
|
abp generate-proxy |
||||
|
```` |
||||
|
|
||||
|
它只会为你自己的应用程序的服务创建代理.它(默认)不会为你使用的应用程序模块创建代理.有几个选项.参见[CLI文档](https://docs.abp.io/en/abp/latest/CLI). |
||||
|
|
||||
|
### 复合主键的CRUD应用服务 |
||||
|
|
||||
|
` CrudAppService `是一个很有用的基类,用来为你的实体创建CRUD应用服务.不过,它不支持**复合主键**的实体. `AbstractKeyCrudAppService`是新开发的基类以支持复合主键的实体.更多信息请浏览[文档](https://docs.abp.io/en/abp/latest/Application-Services#abstractkeycrudappservice). |
||||
|
|
||||
|
### 添加模块的源代码 |
||||
|
|
||||
|
应用程序启动模板带有一些[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index), 以**Nuget和NPM包**的方式**预先安装了** .这样做有几个重要的优点: |
||||
|
|
||||
|
* 当新版本可用时, 你可以 **轻松地[升级](https://docs.abp.io/en/abp/latest/CLI#update)** 这些模块. |
||||
|
* 你的解决方案**更干净**,这样你就可以专注于自己的代码. |
||||
|
|
||||
|
但是,当你需要对一个依赖的模块**大量定制**时,就不如它的代码在你的应用程序中那么容易.为了解决这个问题,我们引入了一个[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)的新命令, 在你的解决方案中用代码**替换**Nuget包.用法很简单: |
||||
|
|
||||
|
````bash |
||||
|
abp add-module --with-source-code |
||||
|
```` |
||||
|
|
||||
|
该命令以源代码方式添加模块, 或者如果模块已经以包引用方式添加了, 则替换为源代码,. |
||||
|
|
||||
|
> 建议在使用此命令前**保存你的更改**到源代码控制系统, 因为它会修改很多你的代码. |
||||
|
|
||||
|
此外,我们也创建了文档来说明如何定制依赖的模块而不改变它们的源代码(见下面的部分).仍然建议以包的方式使用模块,以便在以后可以轻松升级. |
||||
|
|
||||
|
> 免费模块的源代码是**MIT**许可,所以你可以自由更改它们并添加到您的解决方案中. |
||||
|
|
||||
|
### 切换到预览版 |
||||
|
|
||||
|
ABP框架正在迅速发展,我们经常发布新版本.不过,如果你想更紧密地追随它,你可以使用**每日预览包**. |
||||
|
|
||||
|
我们创建了一个ABP CLI命令来轻松地为你的解决方案**更新到最新的预览包**.在你的解决方案的根文件夹中运行以下命令: |
||||
|
|
||||
|
````bash |
||||
|
abp switch-to-preview |
||||
|
```` |
||||
|
|
||||
|
它会修改所有ABP相关的NuGet和NPM包的版本.当你需要时你也可以**切换回最新稳定版**: |
||||
|
|
||||
|
````bash |
||||
|
abp switch-to-stable |
||||
|
```` |
||||
|
|
||||
|
更多信息请浏览[ABP CLI文档](https://docs.abp.io/en/abp/latest/CLI#switch-to-preview). |
||||
|
|
||||
|
### 文档改进 |
||||
|
|
||||
|
#### 扩展/定制依赖应用模块 |
||||
|
|
||||
|
我们创建了一个巨大的文档来说明如何定制模块依赖而不改变其源代码.参见[文档](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Guide). |
||||
|
|
||||
|
除了文档以外,我们已经修订了所有模块([#3166](https://github.com/abpframework/abp/issues/3166)),来使他们的服务更容易扩展和定制. |
||||
|
|
||||
|
#### EF Core迁移指南 |
||||
|
|
||||
|
最近,我们创建了一个指南,说明ABP启动模板所使用的迁移系统. [该指南](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Migrations)还介绍了如何定制迁移结构,拆分你的模块跨多个数据库,复用一个模块的表,等等. |
||||
|
|
||||
|
#### 从 ASP.NET Boilerplate迁移 |
||||
|
|
||||
|
如果你有建立在 ASP.NET Boilerplate的解决方案,我们[创建了一个指南](https://docs.abp.io/en/abp/latest/AspNet-Boilerplate-Migration-Guide),试着帮助迁移你的解决方案到新的ABP框架上. |
||||
|
|
||||
|
### 其他一些功能 |
||||
|
|
||||
|
#### 框架 |
||||
|
|
||||
|
* 添加`IRepository.GetAsync`和`IRepository.FindAsync`方法([#3184](https://github.com/abpframework/abp/issues/3148)). |
||||
|
|
||||
|
#### 模块 |
||||
|
|
||||
|
* 当创建新租户时获取管理员的密码和电子邮件地址,租户管理模块([#3088](https://github.com/abpframework/abp/issues/3088)). |
||||
|
* 集成Elastic全文检索, 文档模块([#2901](https://github.com/abpframework/abp/pull/2901)). |
||||
|
* 新的Quartz后台工作者模块([#2762](https://github.com/abpframework/abp/issues/2762)) |
||||
|
|
||||
|
#### 示例 |
||||
|
|
||||
|
* 微服务演示添加多租户支持([#3032](https://github.com/abpframework/abp/pull/3032)). |
||||
|
|
||||
|
所有的功能, 改善和BUG修复, 请浏览[发布说明](https://github.com/abpframework/abp/releases/tag/2.3.0). |
||||
|
|
||||
|
## 下一步? |
||||
|
|
||||
|
我们未来几个月的目标如下: |
||||
|
|
||||
|
* 完成**文档和示例**,写更多的教程. |
||||
|
* 使框架和现有模块的更加**可定制和可扩展**. |
||||
|
* 集成**gRPC**和为所有预置模块实现gRPC端点([#2882](https://github.com/abpframework/abp/issues/2882)). |
||||
|
* 为ABP框架创建**Blazor UI**, 并在所有模块和启动模板中实现它([#394](https://github.com/abpframework/abp/issues/394)). |
||||
|
* 为预置模块**添加新功能**,并为[ABP商业版](https://commercial.abp.io/)创建新模块. |
||||
|
|
||||
|
更多细节请浏览[GitHub里程碑](https://github.com/abpframework/abp/milestones). |
||||
|
After Width: | Height: | Size: 541 KiB |
|
After Width: | Height: | Size: 179 KiB |
@ -0,0 +1,148 @@ |
|||||
|
# 自定义应用模块: 扩展实体 |
||||
|
|
||||
|
在某些情况下你可能希望为依赖模块中定义的实体添加一些额外的属性(和数据库字段). 本节将介绍一些实现这一目标的不同方法. |
||||
|
|
||||
|
## Extra Properties |
||||
|
|
||||
|
[Extra properties](Entities.md)是一种存储实体的一些额外数据但不用更改实体的方式. 实体应该实现 `IHasExtraProperties` 接口. 所有预构建模块定义的聚合根实体都实现了 `IHasExtraProperties` 接口,所以你可以在这些实体中存储额外的属性. |
||||
|
|
||||
|
示例: |
||||
|
|
||||
|
````csharp |
||||
|
//SET AN EXTRA PROPERTY |
||||
|
var user = await _identityUserRepository.GetAsync(userId); |
||||
|
user.SetProperty("Title", "My custom title value!"); |
||||
|
await _identityUserRepository.UpdateAsync(user); |
||||
|
|
||||
|
//GET AN EXTRA PROPERTY |
||||
|
var user = await _identityUserRepository.GetAsync(userId); |
||||
|
return user.GetProperty<string>("Title"); |
||||
|
```` |
||||
|
|
||||
|
这种方法开箱即用并且非常简单,你可以使用不同的属性名称(如这里的`Title`)在同一时间存储多个属性. |
||||
|
|
||||
|
对于EF Core额外的属性被格式化成单个 `JSON` 字符值串存储在数据库中. 对于MongoDB它们做为单独的字段存储. |
||||
|
|
||||
|
参阅[实体文档](Entities.md)了解更多关于额外系统. |
||||
|
|
||||
|
> 可以基于额外的属性执行**业务逻辑**. 你可以**override**服务方法获取或设置值. 重写服务在下面进行讨论. |
||||
|
|
||||
|
## 创建新实体映射到同一个数据库表/Collection |
||||
|
|
||||
|
尽管额外属性方法**易于使用**并且适用于一些场景,但它具有[实体文档](Entities.md)中描述的一些缺点. |
||||
|
|
||||
|
另一个方法是**创建你自己的实体**映射到**同一个数据库库**(对于MongoDB数据库是collection) |
||||
|
|
||||
|
[应用程序启动模板](Startup-Templates/Application.md)的 `AppUser` 已经实现了这种方法. [EF Core迁移文档](Entity-Framework-Core-Migrations.md)描述了在这些情况下如何实现和管理**EF Core数据库迁移**. 这种方法同样适用于MongoDB,但你不需要处理数据库迁移问题. |
||||
|
|
||||
|
## 创建一个拥有自己数据库表/Collection的新实体 |
||||
|
|
||||
|
映射你的实体到依赖模块的**已存在的表**有一些缺点; |
||||
|
|
||||
|
* 你需要处理EF Core的**数据库迁移架构**. 需要特别注意迁移代码,特别是当你需要在实体间添加**关系**时. |
||||
|
* 你的应用程序数据库和模块数据库将是 **同一个物理数据库**. 通常需要时可以将模块数据库分开,但使用相同的表会对其进行限制. |
||||
|
|
||||
|
如果你想要使你的实体或模块定义的实体**低耦合**,那么可以创建自己的数据库表/collection并且将你的实体映射到自己的数据库表. |
||||
|
|
||||
|
在这种情况下你需要处理**同步问题**,尤其是你要**复制**相关实体的某些属性/字段时,有一些解决方案; |
||||
|
|
||||
|
* 如果你构建的是一个 **单体** 应用程序(或者在同一进程管理你的实体和依赖模块的实体),那么你可以使用[本地事件总线](Local-Event-Bus.md)监听实体更改. |
||||
|
* 如果你构建的是一个 **分布式** 系统,模块的实体和你的实体在不同的 进程/服务 管理(创建/更新/删除),那么你可以使用[分布式事件总线](Distributed-Event-Bus.md)订阅实体的更改事件. |
||||
|
|
||||
|
在你处理事件时,你可以在自己的数据库中更改自己的实体. |
||||
|
|
||||
|
### 订阅本地事件总线 |
||||
|
|
||||
|
[本地事件总线](Local-Event-Bus.md)系统是发布和订阅同一应用程序中发生的事件的方法. |
||||
|
|
||||
|
假设你想要获取 `IdentityUser` 实体的更改信息(创建,更改或删除). 你可以创建一个类实现 `ILocalEventHandler<EntityChangedEventData<IdentityUser>>` 接口. |
||||
|
|
||||
|
````csharp |
||||
|
public class MyLocalIdentityUserChangeEventHandler : |
||||
|
ILocalEventHandler<EntityChangedEventData<IdentityUser>>, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
public async Task HandleEventAsync(EntityChangedEventData<IdentityUser> eventData) |
||||
|
{ |
||||
|
var userId = eventData.Entity.Id; |
||||
|
var userName = eventData.Entity.UserName; |
||||
|
//... |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* `EntityChangedEventData<T>` 涵盖了给定实体的创建,更新或删除事件. 如果你需要你可以分别订阅创建,更新或删除事件(在同一个类或不同的类中). |
||||
|
* 这里的代码在**本地事务之外执行**,因为它监听 `EntityChanged` 事件. 如果当前[工作单元](Unit-Of-Work.md)是事务性的,你可以订阅 `EntityChangingEventData<T>` 事件,它在**同一本地(进行)事务**中执行事件处理. |
||||
|
|
||||
|
> 提醒:这些方法需要在包含处理类的同一进程中更改 `IdentityUser` 实体. 即使在集群环境(同一应用程序的多个实例在不同的服务器进行),它也完美工作. |
||||
|
|
||||
|
### 订阅分布式事件总线 |
||||
|
|
||||
|
[分布式事件总线](Distributed-Event-Bus.md)是在一个应用程序中发布事件,并在相同服务器或不同服务器运行的相同应用程序或不同应用程序中接收事件的方法. |
||||
|
|
||||
|
假设你想要获取 `IdentityUser` 实体的创建,更改或删除信息. 你可以像以下一样创建一个类: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyDistributedIdentityUserChangeEventHandler : |
||||
|
IDistributedEventHandler<EntityCreatedEto<EntityEto>>, |
||||
|
IDistributedEventHandler<EntityUpdatedEto<EntityEto>>, |
||||
|
IDistributedEventHandler<EntityDeletedEto<EntityEto>>, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
public async Task HandleEventAsync(EntityCreatedEto<EntityEto> eventData) |
||||
|
{ |
||||
|
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
||||
|
{ |
||||
|
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
||||
|
//...handle the "created" event |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(EntityUpdatedEto<EntityEto> eventData) |
||||
|
{ |
||||
|
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
||||
|
{ |
||||
|
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
||||
|
//...handle the "updated" event |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(EntityDeletedEto<EntityEto> eventData) |
||||
|
{ |
||||
|
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
||||
|
{ |
||||
|
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
||||
|
//...handle the "deleted" event |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* 它实现了多个 `IDistributedEventHandler` 接口: **创建**,**更改**和**删除**,因为分布式事件总线单独发布事件,没有本地事件总线那样的"Changed"事件. |
||||
|
* 它订阅了 `EntityEto`, 这是一个通用的事件类,ABP框架针对所有类型的实体**自动发布**. 这就是为什么它检查**实体类型**(因为我们没有假设有对 `IdentityUser` 实体有安全的类型引用,所以它是字符串类型的). |
||||
|
|
||||
|
预构建应用模块没有定义专门的事件类型(如`IdentityUserEto` - "ETO" 意思是 "事件传输对象"). 此功能在路线图上([关注这个issue](https://github.com/abpframework/abp/issues/3033)),一旦完成后,你就可以订阅独立的实体类型: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyDistributedIdentityUserCreatedEventHandler : |
||||
|
IDistributedEventHandler<EntityCreatedEto<IdentityUserEto>>, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
public async Task HandleEventAsync(EntityCreatedEto<IdentityUserEto> eventData) |
||||
|
{ |
||||
|
var userId = eventData.Entity.Id; |
||||
|
var userName = eventData.Entity.UserName; |
||||
|
//...handle the "created" event |
||||
|
} |
||||
|
|
||||
|
//... |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* 这个处理程序只会在新用户创建时执行. |
||||
|
|
||||
|
> 唯一预定义的专门事件类是 `UserEto`, 你可以订阅 `EntityCreatedEto<UserEto>` 获取用户创建时的通知. 此事件也适用于身份模块. |
||||
|
|
||||
|
## 另请参阅 |
||||
|
|
||||
|
* [自定义已存在的模块](Customizing-Application-Modules-Guide.md) |
||||
@ -0,0 +1,62 @@ |
|||||
|
# 自定义现有模块 |
||||
|
|
||||
|
ABP框架提供的设计旨在支持构建完全[模块化的应用程序](Module-Development-Basics.md)和系统. 它还提供了一些可以在任何类型的应用程序中**使用**的[预构建应用模块](Modules/Index.md) |
||||
|
|
||||
|
例如,你可以在你的应用程序中**重用**[身份管理模块](Modules/Identity.md)去添加用户,角色和权限管理. [应用程序启动模板](Startup-Templates/Application.md)已经**预装**了Identity和其他模块. |
||||
|
|
||||
|
## 复用应用模块 |
||||
|
|
||||
|
你有两个选项去复用应用模块: |
||||
|
|
||||
|
### 添加包引用 |
||||
|
|
||||
|
你可以添加相关模块的 **NuGet** 和 **NPM** 包引用到你的应用程序,并配置模块(根据它的文档)集成到你的应用程序中. |
||||
|
|
||||
|
正如前面提到,[应用程序启动模板](Startup-Templates/Application.md)已经**预装了一些基本模块**,它引用模块的NuGet和NPM包. |
||||
|
|
||||
|
这种方法具有以下优点: |
||||
|
|
||||
|
* 你的解决方案会非常**干净**,只包含你**自己的应用程序代码**. |
||||
|
* 你可以**很简单的**升级模块到最新的可用模板. `abp update` [CLI](CLI.md) 命令会使更新变的更加简单. 通过这种方式, 你可以获得**最新功能和Bus修复**. |
||||
|
|
||||
|
然而有一个缺点: |
||||
|
|
||||
|
* 你可能无法**自定义**模块,因为模块源码没有在你的解决方案中. |
||||
|
|
||||
|
本文档介绍了 **或者自定义或扩展** 依赖模块并且无需更改其源码,尽快与更改完整的源码比起是有限的,但仍有一些好的方法可以自定义. |
||||
|
|
||||
|
如果你不认为自己会对预构建的模块进行重大更改,那么使用包引用的方法复用模块是推荐的方法. |
||||
|
|
||||
|
### 包含源码 |
||||
|
|
||||
|
如果你想要在预构建的模块上进行**重大**更改或添加**主要功能**,但是可用的扩展点不够使用,那么可以考虑直接使用依赖模块的源码. |
||||
|
|
||||
|
这种情况下,你通常**添加模块源码**到你的解决方案中,并将**包引用替换**为本地项目引用. **[ABP CLI](CLI.md)** 可以为你自动化这一过程. |
||||
|
|
||||
|
#### 分离模块解决方案 |
||||
|
|
||||
|
你可能不希望将模块源代码**直接包含在解决方案**中. 每个模块都包含十多个项目文件,添加**多个模块**会使解决方案变的臃肿可能还会影响**开发时的加载速度**,另外你可能有不同的开发团队维护不同模块. |
||||
|
|
||||
|
无论如何,你都可以为需要的模块创建**单独的解决方案**,将依赖模块做为解决方案中的项目引用. 比如在[abp仓库](https://github.com/abpframework/abp/),我们就是这样做的. |
||||
|
|
||||
|
> 我们看到的一个问题是Visual Studio在这种方式下不能很好的工作(解决方案目录之外对本地项目的引用不能很好地支持). 如果在开发过程中出错(对于外部模块),请在Visual Studio打开应用程序的解决方案后,在命令行运行 `dotnet restore`命令. |
||||
|
|
||||
|
#### 发布的自定义模块的包 |
||||
|
|
||||
|
一个备选方案是将重新打包模块的源代码(NuGet/NPM包),使用包引用. 你可以为公司使用本地私人的Nuget/NPM服务器. |
||||
|
|
||||
|
## 模块自定义/扩展途径 |
||||
|
|
||||
|
如果你决定使用预构建模块的NuGet/NPM包引用方式. 下面的文档详细解释了如何自定义/扩展现有模块的方法: |
||||
|
|
||||
|
* [扩展实体](Customizing-Application-Modules-Extending-Entities.md) |
||||
|
* [重写服务](Customizing-Application-Modules-Overriding-Services.md) |
||||
|
* [重写界面](Customizing-Application-Modules-Overriding-User-Interface.md) |
||||
|
|
||||
|
### 另请参阅 |
||||
|
|
||||
|
另外,请参阅以下文档: |
||||
|
|
||||
|
* 参阅 [本地化文档](Localization.md) 学习如何扩展已存在的本地化资源. |
||||
|
* 参阅 [设置文档](Settings.md) 学习如何更改依赖模块的设置定义. |
||||
|
* 参阅 [授权文档](Authorization.md) 学习如何更改依赖模块的权限定义. |
||||
@ -0,0 +1,166 @@ |
|||||
|
# 自定义应用模块: 重写服务 |
||||
|
|
||||
|
你可能想要**更改**依赖模块的**行为(业务逻辑)**. 在这种情况下,你可以使用[依赖注入](Dependency-Injection.md)的能力替换服务,控制器甚至页面模型到你自己的实现. |
||||
|
|
||||
|
注册到依赖注入的任何类,包括ABP框架的服务都可以被**替换**. |
||||
|
|
||||
|
你可以根据自己的需求使用不同的选项,下面的章节中将介绍这些选项. |
||||
|
|
||||
|
> 请注意,某些服务方法可能不是virtual,你可能无法override,我们会通过设计将其virtual,如果你发现任何方法不可以被覆盖,请[创建一个issue](https://github.com/abpframework/abp/issues/new)或者你直接修改后并发送**pull request**到GitHub. |
||||
|
|
||||
|
## 替换接口 |
||||
|
|
||||
|
如果给定的服务定义了接口,像 `IdentityUserAppService` 类实现了 `IIdentityUserAppService` 接口,你可以为这个接口创建自己的实现并且替换当前的实现. 例如: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`MyIdentityUserAppService` 通过命名约定替换了 `IIdentityUserAppService` 的当前实现. 如果你的类名不匹配,你需要手动公开服务接口: |
||||
|
|
||||
|
````csharp |
||||
|
[ExposeServices(typeof(IIdentityUserAppService))] |
||||
|
public class TestAppService : IIdentityUserAppService, ITransientDependency |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
依赖注入系统允许为一个接口注册多个服务. 注入接口时会解析最后一个注入的服务. 显式的替换服务是一个好习惯. |
||||
|
|
||||
|
示例: |
||||
|
|
||||
|
````csharp |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
[ExposeServices(typeof(IIdentityUserAppService))] |
||||
|
public class TestAppService : IIdentityUserAppService, ITransientDependency |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
使用这种方法, `IIdentityUserAppService` 接口将只会有一个实现. 也可以使用以下方法替换服务: |
||||
|
|
||||
|
````csharp |
||||
|
context.Services.Replace( |
||||
|
ServiceDescriptor.Transient<IIdentityUserAppService, MyIdentityUserAppService>() |
||||
|
); |
||||
|
```` |
||||
|
|
||||
|
你可以在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法编写替换服务代码. |
||||
|
|
||||
|
## 重写一个服务类 |
||||
|
|
||||
|
大多数情况下,你会仅想改变服务当前实现的一个或几个方法. 重新实现完整的接口变的繁琐,更好的方法是继承原始类并重写方法。 |
||||
|
|
||||
|
### 示例: 重写服务方法 |
||||
|
|
||||
|
````csharp |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class MyIdentityUserAppService : IdentityUserAppService |
||||
|
{ |
||||
|
//... |
||||
|
public MyIdentityUserAppService( |
||||
|
IdentityUserManager userManager, |
||||
|
IIdentityUserRepository userRepository, |
||||
|
IGuidGenerator guidGenerator |
||||
|
) : base( |
||||
|
userManager, |
||||
|
userRepository, |
||||
|
guidGenerator) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override async Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input) |
||||
|
{ |
||||
|
if (input.PhoneNumber.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
throw new AbpValidationException( |
||||
|
"Phone number is required for new users!", |
||||
|
new List<ValidationResult> |
||||
|
{ |
||||
|
new ValidationResult( |
||||
|
"Phone number can not be empty!", |
||||
|
new []{"PhoneNumber"} |
||||
|
) |
||||
|
} |
||||
|
); } |
||||
|
|
||||
|
return await base.CreateAsync(input); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
示例中**重写**了 `IdentityUserAppService` [应用程序](Application-Services.md) `CreateAsync` 方法检查手机号码. 然后调用了基类方法继续**基本业务逻辑**. 通过这种方法你可以在基本业务逻辑**之前**和**之后**执行其他业务逻辑. |
||||
|
|
||||
|
你也可以完全**重写**整个业务逻辑去创建用户,而不是调用基类方法. |
||||
|
|
||||
|
### 示例: 重写领域服务 |
||||
|
|
||||
|
````csharp |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
[ExposeServices(typeof(IdentityUserManager))] |
||||
|
public class MyIdentityUserManager : IdentityUserManager |
||||
|
{ |
||||
|
public MyIdentityUserManager( |
||||
|
IdentityUserStore store, |
||||
|
IOptions<IdentityOptions> optionsAccessor, |
||||
|
IPasswordHasher<IdentityUser> passwordHasher, |
||||
|
IEnumerable<IUserValidator<IdentityUser>> userValidators, |
||||
|
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, |
||||
|
ILookupNormalizer keyNormalizer, |
||||
|
IdentityErrorDescriber errors, |
||||
|
IServiceProvider services, |
||||
|
ILogger<IdentityUserManager> logger, |
||||
|
ICancellationTokenProvider cancellationTokenProvider |
||||
|
) : base( |
||||
|
store, |
||||
|
optionsAccessor, |
||||
|
passwordHasher, |
||||
|
userValidators, |
||||
|
passwordValidators, |
||||
|
keyNormalizer, |
||||
|
errors, |
||||
|
services, |
||||
|
logger, |
||||
|
cancellationTokenProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override async Task<IdentityResult> CreateAsync(IdentityUser user) |
||||
|
{ |
||||
|
if (user.PhoneNumber.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
throw new AbpValidationException( |
||||
|
"Phone number is required for new users!", |
||||
|
new List<ValidationResult> |
||||
|
{ |
||||
|
new ValidationResult( |
||||
|
"Phone number can not be empty!", |
||||
|
new []{"PhoneNumber"} |
||||
|
) |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return await base.CreateAsync(user); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
示例中类继承了 `IdentityUserManager` [领域服务](Domain-Services.md),并且重写了 `CreateAsync` 方法进行了与之前相同的手机号码检查. 结果也是一样的,但是这次我们在领域服务实现了它,假设这是我们系统的**核心领域逻辑**. |
||||
|
|
||||
|
> 这里需要 `[ExposeServices(typeof(IdentityUserManager))]` attribute,因为 `IdentityUserManager` 没有定义接口 (像 `IIdentityUserManager`) ,依赖注入系统并不会按照约定公开继承类的服务(如已实现的接口). |
||||
|
|
||||
|
参阅[本地化系统](Localization.md)了解如何自定义错误消息. |
||||
|
|
||||
|
### 重写其他服务 |
||||
|
|
||||
|
控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写. |
||||
|
|
||||
|
## 如何找到服务? |
||||
|
|
||||
|
[模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务. |
||||
@ -0,0 +1,6 @@ |
|||||
|
# 重写用户界面 |
||||
|
|
||||
|
你可以想要重写页面,组件,JavaScript,CSS或你依赖模块的图片文件. 重写UI取决于你使用的UI框架. 选择UI框架以继续: |
||||
|
|
||||
|
* [ASP.NET Core (MVC / Razor Pages)](UI/AspNetCore/Customization-User-Interface.md) |
||||
|
* [Angular](UI/Angular/Customization-User-Interface.md) |
||||
@ -0,0 +1,3 @@ |
|||||
|
# ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南 |
||||
|
|
||||
|
TODO... |
||||
@ -0,0 +1,3 @@ |
|||||
|
# Layout Hooks |
||||
|
|
||||
|
TODO |
||||
@ -0,0 +1,3 @@ |
|||||
|
# Navigation Menu |
||||
|
|
||||
|
TODO |
||||
@ -1,3 +1,26 @@ |
|||||
## ABP Tag Helpers |
# ABP Tag Helpers |
||||
|
|
||||
"ABP tag helpers" 文档还在创建中. 你现在可以参阅[组件演示](http://bootstrap-taghelpers.abp.io/). |
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). |
||||
@ -1,3 +1,3 @@ |
|||||
# Theming |
# ASP.NET Core MVC / Razor Pages 主题 |
||||
|
|
||||
TODO |
TODO |
||||
@ -0,0 +1,3 @@ |
|||||
|
# Toolbars |
||||
|
|
||||
|
TODO |
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 4.5 KiB |