@ -1,8 +1,8 @@ |
|||
<Project> |
|||
<ItemGroup> |
|||
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.1" /> |
|||
<PackageReference Include="Fody" Version="6.0.6"> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.1" PrivateAssets="All" /> |
|||
<PackageReference Include="Fody" Version="6.2.0"> |
|||
<PrivateAssets>All</PrivateAssets> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> |
|||
</PackageReference> |
|||
</ItemGroup> |
|||
|
|||
@ -0,0 +1,63 @@ |
|||
# Switch to EF Core Oracle Devart Provider |
|||
|
|||
This document explains how to switch to the **Oracle** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. |
|||
|
|||
> This document uses a paid library of [Devart](https://www.devart.com/dotconnect/oracle/) company, See [this document](Entity-Framework-Core-Oracle.md) for other options. |
|||
|
|||
## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package |
|||
|
|||
`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.Oracle.Devart](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) package. |
|||
|
|||
## Replace the Module Dependency |
|||
|
|||
Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreOracleDevartModule)` |
|||
|
|||
Also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.Oracle.Devart;`. |
|||
|
|||
## UseOracle() |
|||
|
|||
Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: |
|||
|
|||
* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. |
|||
* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. |
|||
|
|||
|
|||
In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block |
|||
|
|||
```csharp |
|||
var builder = new DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>() |
|||
.UseSqlServer(configuration.GetConnectionString("Default")); |
|||
``` |
|||
|
|||
with this one |
|||
```csharp |
|||
var builder = (DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>) |
|||
new DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>().UseOracle |
|||
( |
|||
configuration.GetConnectionString("Default") |
|||
); |
|||
``` |
|||
|
|||
> Depending on your solution structure, you may find more code files need to be changed. |
|||
|
|||
## Change the Connection Strings |
|||
|
|||
Oracle connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/oracle/ ) for details of Oracle connection string options. |
|||
|
|||
You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. |
|||
|
|||
## Re-Generate the Migrations |
|||
|
|||
The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default. |
|||
EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations. |
|||
|
|||
* Delete the `Migrations` folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. |
|||
* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). |
|||
|
|||
This will scaffold a new migration for Oracle. |
|||
|
|||
Run the `.DbMigrator` project to create the database, apply the changes and seed the initial data. |
|||
|
|||
## Run the Application |
|||
|
|||
It is ready. Just run the application and enjoy coding. |
|||
@ -0,0 +1,58 @@ |
|||
# Switch to EF Core Oracle Provider |
|||
|
|||
This document explains how to switch to the **Oracle** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. |
|||
|
|||
## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package |
|||
|
|||
`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.Oracle](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle) package. |
|||
|
|||
## Replace the Module Dependency |
|||
|
|||
Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreOracleModule)` |
|||
|
|||
Also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.Oracle;`. |
|||
|
|||
## UseOracle() |
|||
|
|||
Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: |
|||
|
|||
* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. |
|||
* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. |
|||
|
|||
|
|||
In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block |
|||
|
|||
```csharp |
|||
var builder = new DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>() |
|||
.UseSqlServer(configuration.GetConnectionString("Default")); |
|||
``` |
|||
|
|||
with this one (just changes `UseSqlServer(...)` to `UseOracle(...)`) |
|||
```csharp |
|||
var builder = new DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>() |
|||
.UseOracle(configuration.GetConnectionString("Default")); |
|||
``` |
|||
|
|||
> Depending on your solution structure, you may find more code files need to be changed. |
|||
|
|||
## Change the Connection Strings |
|||
|
|||
Oracle connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/oracle/ ) for details of Oracle connection string options. |
|||
|
|||
You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. |
|||
|
|||
## Re-Generate the Migrations |
|||
|
|||
The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default. |
|||
EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations. |
|||
|
|||
* Delete the `Migrations` folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. |
|||
* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). |
|||
|
|||
This will scaffold a new migration for Oracle. |
|||
|
|||
Run the `.DbMigrator` project to create the database, apply the changes and seed the initial data. |
|||
|
|||
## Run the Application |
|||
|
|||
It is ready. Just run the application and enjoy coding. |
|||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 56 KiB |
@ -0,0 +1,370 @@ |
|||
# v2.9 to v3.0 Angular UI Migration Guide |
|||
|
|||
## What Changed in v3.0? |
|||
|
|||
### Angular 10 |
|||
|
|||
The new ABP Angular UI is based on Angular 10 and TypeScript 3.9, and we have dropped support for Angular 8. Nevertheless, ABP modules will keep working with Angular 9. Therefore, if your project is Angular 9, you do not need to update to Angular 10. The update is usually very easy though. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
Open a terminal at your root folder and run the following command: |
|||
|
|||
```sh |
|||
yarn ng update @angular/cli @angular/core --force |
|||
``` |
|||
|
|||
This will make the following modifications: |
|||
|
|||
- Update your package.json and install new packages |
|||
- Revise tsconfig.json files to create a "Solution Style" configuration |
|||
- Rename `browserlist` as `.browserlistrc` |
|||
|
|||
On the other hand, it would be better if you check which packages to update first with `yarn ng update` command alone. Angular will give you a list of packages to update. |
|||
|
|||
 |
|||
|
|||
When Angular reports the packages above, your command would look like this: |
|||
|
|||
```sh |
|||
yarn ng update @angular/cli @angular/core ng-zorro-antd --force |
|||
``` |
|||
|
|||
> If Angular complains about uncommited changes in your repo, you can either commit/stash them or add `--allow-dirty` parameter to the command. |
|||
|
|||
|
|||
### Config Modules |
|||
|
|||
In ABP v2.x, every lazy loaded module had a config module available via a separate package and module configuration was as follows: |
|||
|
|||
```ts |
|||
import { AccountConfigModule } from '@abp/ng.account.config'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
// other imports |
|||
AccountConfigModule.forRoot({ redirectUrl: '/' }), |
|||
], |
|||
// providers, declarations, and bootstrap |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
...and in app-routing.module.ts... |
|||
|
|||
```ts |
|||
const routes: Routes = [ |
|||
// other route configuration |
|||
{ |
|||
path: 'account', |
|||
loadChildren: () => import( |
|||
'./lazy-libs/account-wrapper.module' |
|||
).then(m => m.AccountWrapperModule), |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
Although working, this had a few disadvantages: |
|||
|
|||
- Every module came in two independent packages, but in reality, those packages were interdependent. |
|||
- Configuring lazy loaded modules required a wrapper module. |
|||
- ABP Commercial had extensibility system and configuring extensible modules at the root module was increasing the bundle size. |
|||
|
|||
In ABP v3.0, we have introduced a secondary entry points for each config module as well as a new way to configure lazy loaded modules without the wrappers. Now, the module configuration looks like this: |
|||
|
|||
```ts |
|||
import { AccountConfigModule } from '@abp/ng.account/config'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
// other imports |
|||
AccountConfigModule.forRoot(), |
|||
], |
|||
// providers, declarations, and bootstrap |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
...and in app-routing.module.ts... |
|||
|
|||
```ts |
|||
const routes: Routes = [ |
|||
// other route configuration |
|||
{ |
|||
path: 'account', |
|||
loadChildren: () => import('@abp/ng.account') |
|||
.then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
This change helped us reduce bundle size and build times substantially. We believe you will notice the difference in your apps. |
|||
|
|||
#### A Better Example |
|||
|
|||
AppModule: |
|||
|
|||
```ts |
|||
import { AccountConfigModule } from '@abp/ng.account/config'; |
|||
import { CoreModule } from '@abp/ng.core'; |
|||
import { IdentityConfigModule } from '@abp/ng.identity/config'; |
|||
import { SettingManagementConfigModule } from '@abp/ng.setting-management/config'; |
|||
import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config'; |
|||
import { ThemeBasicModule } from '@abp/ng.theme.basic'; |
|||
import { ThemeSharedModule } from '@abp/ng.theme.shared'; |
|||
import { NgModule } from '@angular/core'; |
|||
import { BrowserModule } from '@angular/platform-browser'; |
|||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; |
|||
import { NgxsModule } from '@ngxs/store'; |
|||
import { environment } from '../environments/environment'; |
|||
import { AppRoutingModule } from './app-routing.module'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
BrowserModule, |
|||
BrowserAnimationsModule, |
|||
AppRoutingModule, |
|||
CoreModule.forRoot({ |
|||
environment, |
|||
sendNullsAsQueryParam: false, |
|||
skipGetAppConfiguration: false, |
|||
}), |
|||
ThemeSharedModule.forRoot(), |
|||
AccountConfigModule.forRoot(), |
|||
IdentityConfigModule.forRoot(), |
|||
TenantManagementConfigModule.forRoot(), |
|||
SettingManagementConfigModule.forRoot(), |
|||
ThemeBasicModule.forRoot(), |
|||
NgxsModule.forRoot(), |
|||
], |
|||
// providers, declarations, and bootstrap |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
AppRoutingModule: |
|||
|
|||
```ts |
|||
import { DynamicLayoutComponent } from '@abp/ng.core'; |
|||
import { NgModule } from '@angular/core'; |
|||
import { RouterModule, Routes } from '@angular/router'; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: '', |
|||
component: DynamicLayoutComponent, |
|||
children: [ |
|||
{ |
|||
path: '', |
|||
pathMatch: 'full', |
|||
loadChildren: () => import('./home/home.module') |
|||
.then(m => m.HomeModule), |
|||
}, |
|||
{ |
|||
path: 'account', |
|||
loadChildren: () => import('@abp/ng.account') |
|||
.then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), |
|||
}, |
|||
{ |
|||
path: 'identity', |
|||
loadChildren: () => import('@abp/ng.identity') |
|||
.then(m => m.IdentityModule.forLazy()), |
|||
}, |
|||
{ |
|||
path: 'tenant-management', |
|||
loadChildren: () => import('@abp/ng.tenant-management') |
|||
.then(m => m.TenantManagementModule.forLazy()), |
|||
}, |
|||
{ |
|||
path: 'setting-management', |
|||
loadChildren: () => import('@abp/ng.setting-management') |
|||
.then(m => m.SettingManagementModule.forLazy()), |
|||
}, |
|||
], |
|||
}, |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forRoot(routes)], |
|||
exports: [RouterModule], |
|||
}) |
|||
export class AppRoutingModule {} |
|||
``` |
|||
|
|||
> You may have noticed that we used `DynamicLayoutComponent` at top level route component. We made this change in order to avoid unnecessary renders and flickering. It is not mandatory, but we recommend doing the same in your app routing. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- Remove config packages from your project using `yarn remove`. |
|||
- Import config modules from secondary entry points (e.g. `@abp/ng.identity/config`). |
|||
- Call static `forRoot` method of all new config modules, even if a configuration is not passed. |
|||
- Call static `forRoot` method of `ThemeBasicModule` (or `ThemeLeptonModule` if commercial) and remove `SharedModule` from imports (unless you have added anything that is necessary for your root module in it). |
|||
- Import lazy ABP modules directly in app routing module (e.g. `() => import('@abp/ng.identity').then(...)`). |
|||
- Call static `forLazy` method of all lazy modules inside `then`, even if a configuration is not passed. |
|||
- [OPTIONAL] Add an empty parent route with `DynamicLayoutComponent` for better performance and UX. |
|||
|
|||
|
|||
### RoutesService |
|||
|
|||
In ABP v2.x, adding a route to the menu was done by one of two ways: |
|||
|
|||
- [Via `routes` Property in `AppRoutingModule`](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#via-routes-property-in-approutingmodule) |
|||
- [Via ConfigState](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#via-configstate) |
|||
|
|||
As of v3.0, we have changed how routes are added and modified. We are no longer storing routes in `ConfigState` (breaking change). Instead, there is a new service called `RoutesService` which is used for adding, patching, or removing menu items. Please check [the documentation](./Modifying-the-Menu.md) for details. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- Check if you have ever used `ConfigState` or `ConfigStateService` to add any routes. If so, replace them with `add` method of `RoutesService`. |
|||
- Check if you have ever patched a route. If so, convert them to `patch` method of `RoutesService`. |
|||
- Double-check that you are using absolute paths and providing a `parentName` instead of `children` property for sub-menu items in `add` or `patch` method calls. |
|||
|
|||
|
|||
### NavItemsService |
|||
|
|||
In ABP v2.x, adding a navigation element was done [via LayoutStateService](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#how-to-add-an-element-to-right-part-of-the-menu) |
|||
|
|||
As of v3.0, we have changed how navigation items are added and modified and previous method of doing so is no longer available (breaking change). Please check [the documentation](./Modifying-the-Menu.md) for details. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- Replace all `dispatchAddNavigationElement` calls with `addItems` method of `NavItemsService`. |
|||
|
|||
|
|||
### ngx-datatable |
|||
|
|||
Until v3, we had been using a custom component, `abp-table`, as the default table. However, data grids are complicated components and implementing a fully-featured one requires considerable effort, which we are planning to put in to other features and issues. |
|||
|
|||
As of ABP v3, we have switched to a battle-tested, well-executed data grid: [ngx-datatable](https://github.com/swimlane/ngx-datatable). All ABP modules will come with ngx-datatable already implemented in them. `ThemeSharedModule` already exports `NgxDatatableModule`. So, if you install the package by running `yarn add @swimlane/ngx-datatable` in your terminal, it will be available for use in all modules of your app. |
|||
|
|||
For proper styling, you need to add the following in the styles section of your angular.json file: |
|||
|
|||
```json |
|||
{ |
|||
"input": "node_modules/@swimlane/ngx-datatable/index.css", |
|||
"inject": true, |
|||
"bundleName": "ngx-datatable-index" |
|||
}, |
|||
{ |
|||
"input": "node_modules/@swimlane/ngx-datatable/assets/icons.css", |
|||
"inject": true, |
|||
"bundleName": "ngx-datatable-icons" |
|||
}, |
|||
{ |
|||
"input": "node_modules/@swimlane/ngx-datatable/themes/material.css", |
|||
"inject": true, |
|||
"bundleName": "ngx-datatable-material" |
|||
} |
|||
``` |
|||
|
|||
Since `abp-table` is not dropped yet, modules previously built by ABP v2.x will not suddenly lose all their tables. Yet, they will look and feel different from built-in ABP v3 modules. Therefore, you will probably want to convert the tables in those modules to ngx-datatable. In order to decrease the amount of work required to convert an abp-table into ngx-datatable, we have modified the [ListService](./List-Service.md) to work well with ngx-datatable and [introduced](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates) two new directives: `NgxDatatableListDirective` and `NgxDatatableDefaultDirective`. |
|||
|
|||
The usage of those directives is rather simple: |
|||
|
|||
```ts |
|||
@Component({ |
|||
providers: [ListService], |
|||
}) |
|||
export class SomeComponent { |
|||
data$ = this.list.hookToQuery( |
|||
query => this.dataService.get(query) |
|||
); |
|||
|
|||
constructor( |
|||
public readonly list: ListService, |
|||
public readonly dataService: SomeDataService, |
|||
) {} |
|||
} |
|||
``` |
|||
|
|||
...and in component template... |
|||
|
|||
```html |
|||
<ngx-datatable |
|||
[rows]="(data$ | async)?.items || []" |
|||
[count]="(data$ | async)?.totalCount || 0" |
|||
[list]="list" |
|||
default |
|||
> |
|||
<!-- column templates here --> |
|||
</ngx-datatable> |
|||
``` |
|||
|
|||
Once you bind the injected `ListService` instance through `NgxDatatableListDirective`, you no longer need to worry about pagination or sorting. Similarly, `NgxDatatableDefaultDirective` gets rid of several property bindings to make ngx-datatable fit our styles. |
|||
|
|||
#### A Better Example |
|||
|
|||
```html |
|||
<ngx-datatable |
|||
[rows]="items" |
|||
[count]="count" |
|||
[list]="list" |
|||
default |
|||
> |
|||
<!-- the grid actions column --> |
|||
<ngx-datatable-column |
|||
name="" |
|||
[maxWidth]="150" |
|||
[width]="150" |
|||
[sortable]="false" |
|||
> |
|||
<ng-template |
|||
ngx-datatable-cell-template |
|||
let-row="row" |
|||
let-i="rowIndex" |
|||
> |
|||
<abp-grid-actions |
|||
[index]="i" |
|||
[record]="row" |
|||
text="AbpUi::Actions" |
|||
></abp-grid-actions> |
|||
</ng-template> |
|||
</ngx-datatable-column> |
|||
|
|||
<!-- a basic column --> |
|||
<ngx-datatable-column |
|||
prop="someProp" |
|||
[name]="'::SomeProp' | abpLocalization" |
|||
[width]="200" |
|||
></ngx-datatable-column> |
|||
|
|||
<!-- a column with a custom template --> |
|||
<ngx-datatable-column |
|||
prop="someOtherProp" |
|||
[name]="'::SomeOtherProp' | abpLocalization" |
|||
[width]="250" |
|||
> |
|||
<ng-template |
|||
ngx-datatable-cell-template |
|||
let-row="row" |
|||
let-i="index" |
|||
> |
|||
<div abpEllipsis>{%{{{ row.someOtherProp }}}%}</div> |
|||
</ng-template> |
|||
</ngx-datatable-column> |
|||
</ngx-datatable> |
|||
``` |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- Install `@swimlane/ngx-datatable` package. |
|||
- Add ngx-datatable styles in the angular.json file. |
|||
- If you can, update your modules according to the example above. |
|||
- If you have to do that later and are planning to keep abp-table for a while, make sure you update your pagination according to the [breaking change described here](./List-Service.md). |
|||
|
|||
**Important Note:** The `abp-table` is not removed, but is deprecated and will be removed in the future. Please consider switching to ngx-datatable. |
|||
|
|||
|
|||
### Deprecated Interfaces |
|||
|
|||
Some interfaces have long been marked as deprecated and now they are removed. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- Please check if you are still using [anything listed in this issue](https://github.com/abpframework/abp/issues/4281) |
|||
|
|||
|
|||
## What's Next? |
|||
|
|||
* [Service Proxies](./Service-Proxies.md) |
|||
|
|||
|
After Width: | Height: | Size: 152 KiB |
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53450/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Authentication.OAuth": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:53451/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53760/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.MultiTenancy": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:53763/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:52302/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.Client": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:52303/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:61183/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI.Bootstrap": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"launchUrl": "http://localhost:5000", |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53762/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI.Bundling": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:53765/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:56918/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI.TenantSwitch": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:56919/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53766/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI.Packages": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:53768/" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Uppy |
|||
{ |
|||
public class UppyScriptContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/uppy/uppy.min.js"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Uppy |
|||
{ |
|||
public class UppyStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/uppy/uppy.min.css"); |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:50398/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:50399/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,13 +1,17 @@ |
|||
@using Localization.Resources.AbpUi |
|||
@using Microsoft.Extensions.Localization |
|||
|
|||
@inject IStringLocalizer<AbpUiResource> L |
|||
|
|||
<form method="post" class="page-search-form"> |
|||
<div class="input-group"> |
|||
<input class="form-control page-search-filter-text" placeholder="@L["Search"].Value"> |
|||
<input class="form-control page-search-filter-text" placeholder="@L["Search"].Value" /> |
|||
<div class="input-group-append"> |
|||
<abp-button button-type="Primary" type="submit"> |
|||
<i class="fa fa-search" aria-hidden="true" style="margin-left: 10%; margin-top:50%;" ></i> |
|||
<i class="fa fa-search" aria-hidden="true"></i> |
|||
</abp-button> |
|||
</div> |
|||
</div> |
|||
</form> |
|||
</form> |
|||
|
|||
|
|||
|
|||
@ -0,0 +1,4 @@ |
|||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:51544/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:51545/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53761/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI.Widgets": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:53764/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:55913/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"launchUrl": "http://localhost:5000", |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53767/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:53769/" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Microsoft.AspNetCore.Mvc.DataAnnotations; |
|||
using Microsoft.Extensions.Localization; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations |
|||
{ |
|||
public class AbpValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider |
|||
{ |
|||
private readonly ValidationAttributeAdapterProvider _defaultAdapter; |
|||
|
|||
public AbpValidationAttributeAdapterProvider(ValidationAttributeAdapterProvider defaultAdapter) |
|||
{ |
|||
_defaultAdapter = defaultAdapter; |
|||
} |
|||
|
|||
public virtual IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer) |
|||
{ |
|||
var type = attribute.GetType(); |
|||
|
|||
if (type == typeof(DynamicStringLengthAttribute)) |
|||
{ |
|||
return new DynamicStringLengthAttributeAdapter((DynamicStringLengthAttribute) attribute, stringLocalizer); |
|||
} |
|||
|
|||
if (type == typeof(DynamicMaxLengthAttribute)) |
|||
{ |
|||
return new DynamicMaxLengthAttributeAdapter((DynamicMaxLengthAttribute) attribute, stringLocalizer); |
|||
} |
|||
|
|||
return _defaultAdapter.GetAttributeAdapter(attribute, stringLocalizer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System.Globalization; |
|||
using Microsoft.AspNetCore.Mvc.DataAnnotations; |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; |
|||
using Microsoft.Extensions.Localization; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations |
|||
{ |
|||
public class DynamicMaxLengthAttributeAdapter : AttributeAdapterBase<DynamicMaxLengthAttribute> |
|||
{ |
|||
private readonly string _max; |
|||
|
|||
public DynamicMaxLengthAttributeAdapter( |
|||
DynamicMaxLengthAttribute attribute, |
|||
IStringLocalizer stringLocalizer) |
|||
: base(attribute, stringLocalizer) |
|||
{ |
|||
_max = Attribute.Length.ToString(CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
public override string GetErrorMessage(ModelValidationContextBase validationContext) |
|||
{ |
|||
Check.NotNull(validationContext, nameof(validationContext)); |
|||
|
|||
return GetErrorMessage( |
|||
validationContext.ModelMetadata, |
|||
validationContext.ModelMetadata.GetDisplayName(), |
|||
Attribute.Length |
|||
); |
|||
} |
|||
|
|||
public override void AddValidation(ClientModelValidationContext context) |
|||
{ |
|||
Check.NotNull(context, nameof(context)); |
|||
|
|||
MergeAttribute(context.Attributes, "data-val", "true"); |
|||
MergeAttribute(context.Attributes, "data-val-maxlength", GetErrorMessage(context)); |
|||
MergeAttribute(context.Attributes, "data-val-maxlength-max", _max); |
|||
|
|||
if (Attribute.Length != int.MaxValue) |
|||
{ |
|||
MergeAttribute(context.Attributes, "data-val-maxlength-max", _max); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Microsoft.AspNetCore.Mvc.DataAnnotations; |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; |
|||
using Microsoft.Extensions.Localization; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations |
|||
{ |
|||
public class DynamicStringLengthAttributeAdapter : AttributeAdapterBase<DynamicStringLengthAttribute> |
|||
{ |
|||
private readonly string _max; |
|||
private readonly string _min; |
|||
|
|||
public DynamicStringLengthAttributeAdapter( |
|||
DynamicStringLengthAttribute attribute, |
|||
IStringLocalizer stringLocalizer) |
|||
: base(attribute, stringLocalizer) |
|||
{ |
|||
_max = Attribute.MaximumLength.ToString(CultureInfo.InvariantCulture); |
|||
_min = Attribute.MinimumLength.ToString(CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
public override void AddValidation(ClientModelValidationContext context) |
|||
{ |
|||
Check.NotNull(context, nameof(context)); |
|||
|
|||
MergeAttribute(context.Attributes, "data-val", "true"); |
|||
MergeAttribute(context.Attributes, "data-val-length", GetErrorMessage(context)); |
|||
|
|||
if (Attribute.MaximumLength != int.MaxValue) |
|||
{ |
|||
MergeAttribute(context.Attributes, "data-val-length-max", _max); |
|||
} |
|||
|
|||
if (Attribute.MinimumLength != 0) |
|||
{ |
|||
MergeAttribute(context.Attributes, "data-val-length-min", _min); |
|||
} |
|||
} |
|||
|
|||
public override string GetErrorMessage(ModelValidationContextBase validationContext) |
|||
{ |
|||
Check.NotNull(validationContext, nameof(validationContext)); |
|||
|
|||
return GetErrorMessage( |
|||
validationContext.ModelMetadata, |
|||
validationContext.ModelMetadata.GetDisplayName(), |
|||
Attribute.MaximumLength, |
|||
Attribute.MinimumLength |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:61851/", |
|||
"sslPort": 44301 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Serilog": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53900/", |
|||
"sslPort": 44362 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.SignalR": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53783/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.TestBase": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:53784/" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53374/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:53375/" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.BackgroundWorkers.Quartz |
|||
{ |
|||
public interface IQuartzBackgroundWorkerAdapter : IQuartzBackgroundWorker |
|||
{ |
|||
void BuildWorker(IBackgroundWorker worker); |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
using System; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using Quartz; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.BackgroundWorkers.Quartz |
|||
{ |
|||
public class QuartzPeriodicBackgroundWorkerAdapter<TWorker> : QuartzBackgroundWorkerBase, |
|||
IQuartzBackgroundWorkerAdapter |
|||
where TWorker : IBackgroundWorker |
|||
{ |
|||
private readonly MethodInfo _doWorkAsyncMethod; |
|||
private readonly MethodInfo _doWorkMethod; |
|||
|
|||
public QuartzPeriodicBackgroundWorkerAdapter() |
|||
{ |
|||
AutoRegister = false; |
|||
|
|||
_doWorkAsyncMethod = typeof(TWorker).GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic); |
|||
_doWorkMethod = typeof(TWorker).GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic); |
|||
|
|||
} |
|||
|
|||
public void BuildWorker(IBackgroundWorker worker) |
|||
{ |
|||
int? period; |
|||
var workerType = worker.GetType(); |
|||
|
|||
if (worker is AsyncPeriodicBackgroundWorkerBase || worker is PeriodicBackgroundWorkerBase) |
|||
{ |
|||
if (typeof(TWorker) != worker.GetType()) |
|||
{ |
|||
throw new ArgumentException($"{nameof(worker)} type is different from the generic type"); |
|||
} |
|||
|
|||
var timer = (AbpTimer) worker.GetType().GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker); |
|||
period = timer?.Period; |
|||
} |
|||
else |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (period == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
JobDetail = JobBuilder |
|||
.Create<QuartzPeriodicBackgroundWorkerAdapter<TWorker>>() |
|||
.WithIdentity(workerType.FullName) |
|||
.Build(); |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity(workerType.FullName) |
|||
.WithSimpleSchedule(builder => builder.WithInterval(TimeSpan.FromMilliseconds(period.Value)).RepeatForever()) |
|||
.Build(); |
|||
} |
|||
|
|||
public override async Task Execute(IJobExecutionContext context) |
|||
{ |
|||
var worker = (IBackgroundWorker) ServiceProvider.GetService(typeof(TWorker)); |
|||
var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider); |
|||
|
|||
switch (worker) |
|||
{ |
|||
case AsyncPeriodicBackgroundWorkerBase asyncWorker: |
|||
{ |
|||
if (_doWorkAsyncMethod != null) |
|||
{ |
|||
await (Task) _doWorkAsyncMethod.Invoke(asyncWorker, new object[] {workerContext}); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
case PeriodicBackgroundWorkerBase syncWorker: |
|||
{ |
|||
if (_doWorkMethod != null) |
|||
{ |
|||
_doWorkMethod.Invoke(syncWorker, new object[] {workerContext}); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
using System.Text.RegularExpressions; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Azure |
|||
{ |
|||
public class AzureBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency |
|||
{ |
|||
/// <summary>
|
|||
///https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
|
|||
/// </summary>
|
|||
public virtual string NormalizeContainerName(string containerName) |
|||
{ |
|||
// All letters in a container name must be lowercase.
|
|||
containerName = containerName.ToLower(); |
|||
|
|||
// Container names can contain only letters, numbers, and the dash (-) character.
|
|||
containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); |
|||
|
|||
// Every dash (-) character must be immediately preceded and followed by a letter or number;
|
|||
// consecutive dashes are not permitted in container names.
|
|||
// Container names must start or end with a letter or number
|
|||
containerName = Regex.Replace(containerName, "-{2,}", "-"); |
|||
containerName = Regex.Replace(containerName, "^-", string.Empty); |
|||
containerName = Regex.Replace(containerName, "-$", string.Empty); |
|||
|
|||
// Container names must be from 3 through 63 characters long.
|
|||
if (containerName.Length < 3) |
|||
{ |
|||
var length = containerName.Length; |
|||
for (var i = 0; i < 3 - length; i++) |
|||
{ |
|||
containerName += "0"; |
|||
} |
|||
} |
|||
|
|||
if (containerName.Length > 63) |
|||
{ |
|||
containerName = containerName.Substring(0, 63); |
|||
} |
|||
|
|||
return containerName; |
|||
} |
|||
|
|||
/// <summary>
|
|||
///https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#blob-names
|
|||
/// </summary>
|
|||
public virtual string NormalizeBlobName(string blobName) |
|||
{ |
|||
return blobName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System; |
|||
using System.Runtime; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text.RegularExpressions; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.BlobStoring.FileSystem |
|||
{ |
|||
public class FileSystemBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency |
|||
{ |
|||
private readonly IOSPlatformProvider _iosPlatformProvider; |
|||
|
|||
public FileSystemBlobNamingNormalizer(IOSPlatformProvider iosPlatformProvider) |
|||
{ |
|||
_iosPlatformProvider = iosPlatformProvider; |
|||
} |
|||
|
|||
public virtual string NormalizeContainerName(string containerName) |
|||
{ |
|||
return Normalize(containerName); |
|||
} |
|||
|
|||
public virtual string NormalizeBlobName(string blobName) |
|||
{ |
|||
return Normalize(blobName); |
|||
} |
|||
|
|||
protected virtual string Normalize(string fileName) |
|||
{ |
|||
var os = _iosPlatformProvider.GetCurrentOSPlatform(); |
|||
if (os == OSPlatform.Windows) |
|||
{ |
|||
// A filename cannot contain any of the following characters: \ / : * ? " < > |
|
|||
// In order to support the directory included in the blob name, remove / and \
|
|||
fileName = Regex.Replace(fileName, "[:\\*\\?\"<>\\|]", string.Empty); |
|||
} |
|||
|
|||
return fileName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.BlobStoring |
|||
{ |
|||
public interface IBlobNamingNormalizer |
|||
{ |
|||
string NormalizeContainerName(string containerName); |
|||
|
|||
string NormalizeBlobName(string blobName); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,25 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AssemblyName>Volo.Abp.Caching.StackExchangeRedis</AssemblyName> |
|||
<PackageId>Volo.Abp.Caching.StackExchangeRedis</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Caching\Volo.Abp.Caching.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="3.1.5" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Caching.StackExchangeRedis |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpCachingModule) |
|||
)] |
|||
public class AbpCachingStackExchangeRedisModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
context.Services.AddStackExchangeRedisCache(options => |
|||
{ |
|||
var redisConfiguration = configuration["Redis:Configuration"]; |
|||
if (!redisConfiguration.IsNullOrEmpty()) |
|||
{ |
|||
options.Configuration = configuration["Redis:Configuration"]; |
|||
} |
|||
}); |
|||
|
|||
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache, AbpRedisCache>()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,294 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Caching.StackExchangeRedis; |
|||
using Microsoft.Extensions.Options; |
|||
using StackExchange.Redis; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Caching.StackExchangeRedis |
|||
{ |
|||
[DisableConventionalRegistration] |
|||
public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems |
|||
{ |
|||
protected static readonly string SetScript; |
|||
protected static readonly string AbsoluteExpirationKey; |
|||
protected static readonly string SlidingExpirationKey; |
|||
protected static readonly string DataKey; |
|||
protected static readonly long NotPresent; |
|||
|
|||
private static readonly FieldInfo RedisDatabaseField; |
|||
private static readonly MethodInfo ConnectMethod; |
|||
private static readonly MethodInfo ConnectAsyncMethod; |
|||
private static readonly MethodInfo MapMetadataMethod; |
|||
private static readonly MethodInfo GetAbsoluteExpirationMethod; |
|||
private static readonly MethodInfo GetExpirationInSecondsMethod; |
|||
|
|||
protected IDatabase RedisDatabase => GetRedisDatabase(); |
|||
private IDatabase _redisDatabase; |
|||
|
|||
protected string Instance { get; } |
|||
|
|||
static AbpRedisCache() |
|||
{ |
|||
var type = typeof(RedisCache); |
|||
|
|||
RedisDatabaseField = type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic); |
|||
|
|||
ConnectMethod = type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic); |
|||
|
|||
ConnectAsyncMethod = type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic); |
|||
|
|||
MapMetadataMethod = type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic); |
|||
|
|||
GetAbsoluteExpirationMethod = type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic); |
|||
|
|||
GetExpirationInSecondsMethod = type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic); |
|||
|
|||
SetScript = type.GetField("SetScript", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); |
|||
|
|||
AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); |
|||
|
|||
SlidingExpirationKey = type.GetField("SlidingExpirationKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); |
|||
|
|||
DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); |
|||
|
|||
NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).To<int>(); |
|||
} |
|||
|
|||
public AbpRedisCache(IOptions<RedisCacheOptions> optionsAccessor) |
|||
: base(optionsAccessor) |
|||
{ |
|||
Instance = optionsAccessor.Value.InstanceName ?? string.Empty; |
|||
} |
|||
|
|||
protected virtual void Connect() |
|||
{ |
|||
if (GetRedisDatabase() != null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
ConnectMethod.Invoke(this, Array.Empty<object>()); |
|||
} |
|||
|
|||
protected virtual Task ConnectAsync(CancellationToken token = default) |
|||
{ |
|||
if (GetRedisDatabase() != null) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
return (Task) ConnectAsyncMethod.Invoke(this, new object[] {token}); |
|||
} |
|||
|
|||
public byte[][] GetMany( |
|||
IEnumerable<string> keys) |
|||
{ |
|||
keys = Check.NotNull(keys, nameof(keys)); |
|||
|
|||
return GetAndRefreshMany(keys, true); |
|||
} |
|||
|
|||
public async Task<byte[][]> GetManyAsync( |
|||
IEnumerable<string> keys, |
|||
CancellationToken token = default) |
|||
{ |
|||
keys = Check.NotNull(keys, nameof(keys)); |
|||
|
|||
return await GetAndRefreshManyAsync(keys, true, token); |
|||
} |
|||
|
|||
public void SetMany( |
|||
IEnumerable<KeyValuePair<string, byte[]>> items, |
|||
DistributedCacheEntryOptions options) |
|||
{ |
|||
Connect(); |
|||
|
|||
Task.WaitAll(PipelineSetMany(items, options)); |
|||
} |
|||
|
|||
public async Task SetManyAsync( |
|||
IEnumerable<KeyValuePair<string, byte[]>> items, |
|||
DistributedCacheEntryOptions options, |
|||
CancellationToken token = default) |
|||
{ |
|||
token.ThrowIfCancellationRequested(); |
|||
|
|||
await ConnectAsync(token); |
|||
|
|||
await Task.WhenAll(PipelineSetMany(items, options)); |
|||
} |
|||
|
|||
protected virtual byte[][] GetAndRefreshMany( |
|||
IEnumerable<string> keys, |
|||
bool getData) |
|||
{ |
|||
Connect(); |
|||
|
|||
var keyArray = keys.Select(key => Instance + key).ToArray(); |
|||
RedisValue[][] results; |
|||
|
|||
if (getData) |
|||
{ |
|||
results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey, |
|||
SlidingExpirationKey, DataKey); |
|||
} |
|||
else |
|||
{ |
|||
results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey, |
|||
SlidingExpirationKey); |
|||
} |
|||
|
|||
Task.WaitAll(PipelineRefreshManyAndOutData(keyArray, results, out var bytes)); |
|||
|
|||
return bytes; |
|||
} |
|||
|
|||
protected virtual async Task<byte[][]> GetAndRefreshManyAsync( |
|||
IEnumerable<string> keys, |
|||
bool getData, |
|||
CancellationToken token = default) |
|||
{ |
|||
token.ThrowIfCancellationRequested(); |
|||
|
|||
await ConnectAsync(token); |
|||
|
|||
var keyArray = keys.Select(key => Instance + key).ToArray(); |
|||
RedisValue[][] results; |
|||
|
|||
if (getData) |
|||
{ |
|||
results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, |
|||
SlidingExpirationKey, DataKey); |
|||
} |
|||
else |
|||
{ |
|||
results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, |
|||
SlidingExpirationKey); |
|||
} |
|||
|
|||
await Task.WhenAll(PipelineRefreshManyAndOutData(keyArray, results, out var bytes)); |
|||
|
|||
return bytes; |
|||
} |
|||
|
|||
protected virtual Task[] PipelineRefreshManyAndOutData( |
|||
string[] keys, |
|||
RedisValue[][] results, |
|||
out byte[][] bytes) |
|||
{ |
|||
bytes = new byte[keys.Length][]; |
|||
var tasks = new Task[keys.Length]; |
|||
|
|||
for (var i = 0; i < keys.Length; i++) |
|||
{ |
|||
if (results[i].Length >= 2) |
|||
{ |
|||
MapMetadata(results[i], out DateTimeOffset? absExpr, out TimeSpan? sldExpr); |
|||
|
|||
if (sldExpr.HasValue) |
|||
{ |
|||
TimeSpan? expr; |
|||
|
|||
if (absExpr.HasValue) |
|||
{ |
|||
var relExpr = absExpr.Value - DateTimeOffset.Now; |
|||
expr = relExpr <= sldExpr.Value ? relExpr : sldExpr; |
|||
} |
|||
else |
|||
{ |
|||
expr = sldExpr; |
|||
} |
|||
|
|||
tasks[i] = RedisDatabase.KeyExpireAsync(keys[i], expr); |
|||
} |
|||
else |
|||
{ |
|||
tasks[i] = Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
if (results[i].Length >= 3 && results[i][2].HasValue) |
|||
{ |
|||
bytes[i] = results[i][2]; |
|||
} |
|||
else |
|||
{ |
|||
bytes[i] = null; |
|||
} |
|||
} |
|||
|
|||
return tasks; |
|||
} |
|||
|
|||
protected virtual Task[] PipelineSetMany( |
|||
IEnumerable<KeyValuePair<string, byte[]>> items, |
|||
DistributedCacheEntryOptions options) |
|||
{ |
|||
items = Check.NotNull(items, nameof(items)); |
|||
options = Check.NotNull(options, nameof(options)); |
|||
|
|||
var itemArray = items.ToArray(); |
|||
var tasks = new Task[itemArray.Length]; |
|||
var creationTime = DateTimeOffset.UtcNow; |
|||
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); |
|||
|
|||
for (var i = 0; i < itemArray.Length; i++) |
|||
{ |
|||
tasks[i] = RedisDatabase.ScriptEvaluateAsync(SetScript, new RedisKey[] {Instance + itemArray[i].Key}, |
|||
new RedisValue[] |
|||
{ |
|||
absoluteExpiration?.Ticks ?? NotPresent, |
|||
options.SlidingExpiration?.Ticks ?? NotPresent, |
|||
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, |
|||
itemArray[i].Value |
|||
}); |
|||
} |
|||
|
|||
return tasks; |
|||
} |
|||
|
|||
protected virtual void MapMetadata( |
|||
RedisValue[] results, |
|||
out DateTimeOffset? absoluteExpiration, |
|||
out TimeSpan? slidingExpiration) |
|||
{ |
|||
var parameters = new object[] {results, null, null}; |
|||
MapMetadataMethod.Invoke(this, parameters); |
|||
|
|||
absoluteExpiration = (DateTimeOffset?) parameters[1]; |
|||
slidingExpiration = (TimeSpan?) parameters[2]; |
|||
} |
|||
|
|||
protected virtual long? GetExpirationInSeconds( |
|||
DateTimeOffset creationTime, |
|||
DateTimeOffset? absoluteExpiration, |
|||
DistributedCacheEntryOptions options) |
|||
{ |
|||
return (long?) GetExpirationInSecondsMethod.Invoke(null, |
|||
new object[] {creationTime, absoluteExpiration, options}); |
|||
} |
|||
|
|||
protected virtual DateTimeOffset? GetAbsoluteExpiration( |
|||
DateTimeOffset creationTime, |
|||
DistributedCacheEntryOptions options) |
|||
{ |
|||
return (DateTimeOffset?) GetAbsoluteExpirationMethod.Invoke(null, new object[] {creationTime, options}); |
|||
} |
|||
|
|||
private IDatabase GetRedisDatabase() |
|||
{ |
|||
if (_redisDatabase == null) |
|||
{ |
|||
_redisDatabase = RedisDatabaseField.GetValue(this) as IDatabase; |
|||
} |
|||
|
|||
return _redisDatabase; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using StackExchange.Redis; |
|||
|
|||
namespace Volo.Abp.Caching.StackExchangeRedis |
|||
{ |
|||
public static class AbpRedisExtensions |
|||
{ |
|||
public static RedisValue[][] HashMemberGetMany( |
|||
this IDatabase cache, |
|||
string[] keys, |
|||
params string[] members) |
|||
{ |
|||
var tasks = new Task<RedisValue[]>[keys.Length]; |
|||
var fields = members.Select(member => (RedisValue) member).ToArray(); |
|||
var results = new RedisValue[keys.Length][]; |
|||
|
|||
for (var i = 0; i < keys.Length; i++) |
|||
{ |
|||
tasks[i] = cache.HashGetAsync((RedisKey) keys[i], fields); |
|||
} |
|||
|
|||
for (var i = 0; i < tasks.Length; i++) |
|||
{ |
|||
results[i] = cache.Wait(tasks[i]); |
|||
} |
|||
|
|||
return results; |
|||
} |
|||
|
|||
public static async Task<RedisValue[][]> HashMemberGetManyAsync( |
|||
this IDatabase cache, |
|||
string[] keys, |
|||
params string[] members) |
|||
{ |
|||
var tasks = new Task<RedisValue[]>[keys.Length]; |
|||
var fields = members.Select(member => (RedisValue) member).ToArray(); |
|||
|
|||
for (var i = 0; i < keys.Length; i++) |
|||
{ |
|||
tasks[i] = cache.HashGetAsync((RedisKey) keys[i], fields); |
|||
} |
|||
|
|||
return await Task.WhenAll(tasks); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
|
|||
namespace Volo.Abp.Caching |
|||
{ |
|||
public interface ICacheSupportsMultipleItems |
|||
{ |
|||
byte[][] GetMany( |
|||
IEnumerable<string> keys |
|||
); |
|||
|
|||
Task<byte[][]> GetManyAsync( |
|||
IEnumerable<string> keys, |
|||
CancellationToken token = default |
|||
); |
|||
|
|||
void SetMany( |
|||
IEnumerable<KeyValuePair<string, byte[]>> items, |
|||
DistributedCacheEntryOptions options |
|||
); |
|||
|
|||
Task SetManyAsync( |
|||
IEnumerable<KeyValuePair<string, byte[]>> items, |
|||
DistributedCacheEntryOptions options, |
|||
CancellationToken token = default |
|||
); |
|||
} |
|||
} |
|||
@ -1,7 +0,0 @@ |
|||
{ |
|||
"profiles": { |
|||
"Volo.Abp.Cli": { |
|||
"commandName": "Project" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace System.Runtime |
|||
{ |
|||
public interface IOSPlatformProvider |
|||
{ |
|||
OSPlatform GetCurrentOSPlatform(); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System.Runtime.InteropServices; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace System.Runtime |
|||
{ |
|||
public class OSPlatformProvider : IOSPlatformProvider, ITransientDependency |
|||
{ |
|||
public virtual OSPlatform GetCurrentOSPlatform() |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|||
{ |
|||
return OSPlatform.OSX; //MAC
|
|||
} |
|||
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
return OSPlatform.Windows; |
|||
} |
|||
|
|||
return OSPlatform.Linux; |
|||
} |
|||
} |
|||
} |
|||