Browse Source

Merge branch 'dev' into liangshiwei/virtualfilesystem-explorer-module

pull/3971/head
liangshiwei 6 years ago
parent
commit
a41d2bbc17
  1. 2
      .github/workflows/build-and-test.yml
  2. 4
      README.md
  3. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  4. 21
      docs/en/Dapper.md
  5. 47
      docs/en/Object-Extensions.md
  6. 14
      docs/en/Text-Templating.md
  7. 165
      docs/en/UI/Angular/List-Service.md
  8. 6
      docs/en/UI/Angular/Track-By-Service.md
  9. 4
      docs/en/docs-nav.json
  10. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/EntityExtensionDto.cs
  11. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionEnumDto.cs
  12. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionEnumFieldDto.cs
  13. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyAttributeDto.cs
  14. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyDto.cs
  15. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ObjectExtensionsDto.cs
  16. 38
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperLocalizer.cs
  17. 21
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperExtensions.cs
  18. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs
  19. 56
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs
  20. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs
  21. 68
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs
  22. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/IAbpTagHelperLocalizer.cs
  23. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs
  24. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs
  25. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerStyleContributor.cs
  26. 16
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JQueryFormScriptContributor.cs
  27. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JsTreeOptions.cs
  28. 26
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JsTreeStyleContributor.cs
  29. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Luxon/LuxonScriptContributor.cs
  30. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
  31. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs
  32. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js
  33. 30
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  34. 100
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js
  35. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs
  36. 150
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs
  37. 29
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
  38. 90
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
  39. 95
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyAttributeDtoFactory.cs
  40. 0
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ICachedObjectExtensionsDtoService.cs
  41. 9
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/IExtensionPropertyAttributeDtoFactory.cs
  42. 1
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertiesDictionaryModelBinderProvider.cs
  43. 6
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs
  44. 97
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/ObjectExtending/ObjectExtendingPropertyInfoExtensions.cs
  45. 58
      framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpHub.cs
  46. 19
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs
  47. 10
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs
  48. 46
      framework/src/Volo.Abp.Core/Volo/Abp/AbpInitializationException.cs
  49. 27
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs
  50. 22
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
  51. 97
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
  52. 29
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  53. 1
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  54. 47
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
  55. 0
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs
  56. 0
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizationResourceNameAttribute.cs
  57. 2
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
  58. 50
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StringLocalizerHelper.cs
  59. 9
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
  60. 37
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensionPropertyHelper.cs
  61. 38
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/IBasicObjectExtensionPropertyInfo.cs
  62. 20
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs
  63. 24
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfiguration.cs
  64. 32
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs
  65. 4
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs
  66. 25
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  67. 22
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/TypeHelper_Tests.cs
  68. 6
      framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json
  69. 132
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs
  70. 12
      modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs
  71. 6
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml
  72. 7
      modules/blogging/src/Volo.Blogging.Web/SocialMedia/BloggingTwitterOptions.cs
  73. 23
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs
  74. 28
      npm/ng-packs/package.json
  75. 2
      npm/ng-packs/packages/core/package.json
  76. 1
      npm/ng-packs/packages/core/src/lib/services/index.ts
  77. 101
      npm/ng-packs/packages/core/src/lib/services/list.service.ts
  78. 153
      npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts
  79. 1
      npm/ng-packs/packages/core/src/lib/tokens/index.ts
  80. 3
      npm/ng-packs/packages/core/src/lib/tokens/list.token.ts
  81. 2
      npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts
  82. 5
      npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.scss
  83. 63
      npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts
  84. 141
      npm/ng-packs/yarn.lock
  85. 5
      npm/packs/jstree/abp.resourcemapping.js
  86. 12
      npm/packs/jstree/package.json
  87. 2
      samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs
  88. 6
      samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminAppHostModule.cs
  89. 6
      samples/MicroserviceDemo/applications/PublicWebSite.Host/PublicWebSiteHostModule.cs
  90. 4
      samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingServiceHostModule.cs
  91. 5
      samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityServiceHostModule.cs
  92. 4
      samples/MicroserviceDemo/microservices/ProductService.Host/ProductServiceHostModule.cs
  93. 4
      samples/MicroserviceDemo/microservices/TenantManagementService.Host/TenantManagementServiceHostModule.cs
  94. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Client/MyCompanyName.MyProjectName.HttpApi.Client.csproj
  95. 6
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs
  96. 8
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs
  97. 8
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs
  98. 8
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs
  99. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs
  100. 6
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/Index.cshtml

2
.github/workflows/main.yml → .github/workflows/build-and-test.yml

@ -1,4 +1,4 @@
name: "Main"
name: "build and test"
on:
pull_request:
paths:

4
README.md

@ -1,9 +1,9 @@
# ABP
[![Build Status](http://vjenkins.dynu.net:5480/job/abp/badge/icon)](http://ci.volosoft.com:5480/blue/organizations/jenkins/abp/activity)
![build and test](https://github.com/abpframework/abp/workflows/build%20and%20test/badge.svg)
[![NuGet](https://img.shields.io/nuget/v/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![MyGet (with prereleases)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://docs.abp.io/en/abp/latest/Nightly-Builds)
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
This project is the next generation of the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) web application framework. See [the announcement](https://blog.abp.io/abp/Abp-vNext-Announcement).

9
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -127,7 +127,7 @@
"Generate": "Generate",
"MissingQuantityField": "The quantity field is required!",
"MissingPriceField": "The Price field is required!",
"CodeUsageStatus": "Code Usage Status",
"CodeUsageStatus": "Status",
"Country": "Country",
"DeveloperCount": "Developer Count",
"RequestCode": "Request Code",
@ -147,6 +147,11 @@
"EmailSent": "Email Sent",
"SuccessfullySent": "Successfully Sent",
"SuccessfullyDeleted": "Successfully Deleted",
"DiscountRequestDeletionWarningMessage": "Discount request will be deleted"
"DiscountRequestDeletionWarningMessage": "Discount request will be deleted" ,
"BusinessType": "Business Type",
"TotalQuestionCount": "Total question count",
"RemainingQuestionCount": "Remaining question count",
"TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !",
"QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !"
}
}

21
docs/en/Dapper.md

@ -1,22 +1,24 @@
# Dapper Integration
Because Dapper's idea is that the sql statement takes precedence, and mainly provides some extension methods for the `IDbConnection` interface.
Dapper is a light-weight and simple database provider. The major benefit of using Dapper is writing T-SQL queries. It provides some extension methods for `IDbConnection` interface.
Abp does not encapsulate too many functions for Dapper. Abp Dapper provides a `DapperRepository<TDbContext>` base class based on Abp EntityFrameworkCore, which provides the `IDbConnection` and `IDbTransaction` properties required by Dapper.
These two properties can work well with [Unit-Of-Work](Unit-Of-Work.md).
ABP does not encapsulate many functions for Dapper. ABP Dapper library provides a `DapperRepository<TDbContext>` base class based on ABP EntityFrameworkCore module, which provides the `IDbConnection` and `IDbTransaction` properties required by Dapper. `IDbConnection` and `IDbTransaction` works well with the [ABP Unit-Of-Work](Unit-Of-Work.md).
## Installation
Please install and configure EF Core according to [EF Core's integrated documentation](Entity-Framework-Core.md).
Install and configure EF Core according to [EF Core's integrated documentation](Entity-Framework-Core.md).
`Volo.Abp.Dapper` is the library for the Dapper integration.
You can find it on NuGet Gallery: https://www.nuget.org/packages/Volo.Abp.Dapper
`Volo.Abp.Dapper` is the main nuget package for the Dapper integration. Install it to your project (for a layered application, to your data/infrastructure layer):
Install it to your project (for a layered application, to your data/infrastructure layer):
```shell
Install-Package Volo.Abp.Dapper
```
Then add `AbpDapperModule` module dependency (`DependsOn` attribute) to your [module](Module-Development-Basics.md):
Then add `AbpDapperModule` module dependency (with `DependsOn` attribute) to your [module](Module-Development-Basics.md):
````C#
using Volo.Abp.Dapper;
@ -34,9 +36,10 @@ namespace MyCompany.MyProject
## Implement Dapper Repository
The following code implements the `Person` repository, which requires EF Core's `DbContext` (MyAppDbContext). You can inject `PersonDapperRepository` to call its methods.
The following code creates the `PersonRepository`, which requires EF Core's `DbContext` (MyAppDbContext).
You can inject `PersonDapperRepository` to your services for your database operations.
`DbConnection` and `DbTransaction` are from the `DapperRepository` base class.
`DbConnection` and `DbTransaction` comes from the `DapperRepository` base class.
```C#
public class PersonDapperRepository : DapperRepository<MyAppDbContext>, ITransientDependency

47
docs/en/Object-Extensions.md

@ -174,6 +174,44 @@ ObjectExtensionManager.Instance
The following sections explain the fundamental property configuration options.
#### Default Value
A default value is automatically set for the new property, which is the natural default value for the property type, like `null` for `string`, `false` for `bool` or `0` for `int`.
There are two ways to override the default value:
##### DefaultValue Option
`DefaultValue` option can be set to any value:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, int>(
"MyIntProperty",
options =>
{
options.DefaultValue = 42;
});
````
##### DefaultValueFactory Options
`DefaultValueFactory` can be set to a function that returns the default value:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, DateTime>(
"MyDateTimeProperty",
options =>
{
options.DefaultValueFactory = () => DateTime.Now;
});
````
`options.DefaultValueFactory` has a higher priority than the `options.DefaultValue` .
> Tip: Use `DefaultValueFactory` option only if the default value may change over the time (like `DateTime.Now` in this example). If it is a constant value, then use the `DefaultValue` option.
#### CheckPairDefinitionOnMapping
Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better.
@ -208,6 +246,15 @@ ObjectExtensionManager.Instance
With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided.
#### Default Validation Attributes
There are some attributes **automatically added** when you create certain type of properties;
* `RequiredAttribute` is added for non nullable primitive property types (e.g. `int`, `bool`, `DateTime`...) and `enum` types.
* `EnumDataTypeAttribute` is added for enum types, to prevent to set invalid enum values.
Use `options.Attributes.Clear();` if you don't want these attributes.
### Custom Validation
If you need, you can add a custom action that is executed to validate the extra properties. Example:

14
docs/en/Text-Templating.md

@ -17,7 +17,7 @@ You can use the rendered output for any purpose, like sending emails or preparin
Here, a simple template:
````
Hello {{model.name}} :)
Hello {%{{{model.name}}}%} :)
````
You can define a class with a `Name` property to render this template:
@ -114,7 +114,7 @@ public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
Example `Hello.tpl` content is shown below:
````
Hello {{model.name}} :)
Hello {%{{{model.name}}}%} :)
````
The [Virtual File System](Virtual-File-System.md) requires to add your files in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class:
@ -204,7 +204,7 @@ Inline localization uses the [localization system](Localization.md) to localize
Assuming you need to send an email to a user to reset her/his password. Here, the template content:
````
<a href="{{model.link}}">{{L "ResetMyPassword"}}</a>
<a href="{%{{{model.link}}}%}">{%{{{L "ResetMyPassword"}}}%}</a>
````
`L` function is used to localize the given key based on the current user culture. You need to define the `ResetMyPassword` key inside your localization file:
@ -314,12 +314,12 @@ First, create a template file just like before:
<meta charset="utf-8" />
</head>
<body>
{{content}}
{%{{{content}}}%}
</body>
</html>
````
* A layout template must have a **{{content}}** part as a place holder for the rendered child content.
* A layout template must have a **{%{{{content}}}%}** part as a place holder for the rendered child content.
The register your template in the template definition provider:
@ -357,7 +357,7 @@ ABP passes the `model` that can be used to access to the model inside the templa
An example template content:
````
A global object value: {{myGlobalObject}}
A global object value: {%{{{myGlobalObject}}}%}
````
This template assumes that that is a `myGlobalObject` object in the template rendering context. You can provide it like shown below:
@ -413,7 +413,7 @@ public class TemplateContentDemo : ITransientDependency
The result will be the raw template content:
````
Hello {{model.name}} :)
Hello {%{{{model.name}}}%} :)
````
* `GetContentOrNullAsync` returns `null` if no content defined for the requested template.

165
docs/en/UI/Angular/List-Service.md

@ -0,0 +1,165 @@
# Querying Lists Easily with ListService
`ListService` is a utility service to provide an easy pagination, sorting, and search implementation.
## Getting Started
`ListService` is **not provided in root**. The reason is, this way, it will clear any subscriptions on component destroy. You may use the optional `LIST_QUERY_DEBOUNCE_TIME` token to adjust the debounce behavior.
```js
import { ListService } from '@abp/ng.core';
import { BookDto } from '../models';
import { BookService } from '../services';
@Component({
/* class metadata here */
providers: [
// [Required]
ListService,
// [Optional]
// Provide this token if you want a different debounce time.
// Default is 300. Cannot be 0. Any value below 100 is not recommended.
{ provide: LIST_QUERY_DEBOUNCE_TIME, useValue: 500 },
],
template: `
`,
})
class BookComponent {
items: BookDto[] = [];
count = 0;
constructor(
public readonly list: ListService,
private bookService: BookService,
) {}
ngOnInit() {
// A function that gets query and returns an observable
const bookStreamCreator = query => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe(
response => {
this.items = response.items;
this.count = response.count;
// If you use OnPush change detection strategy,
// call detectChanges method of ChangeDetectorRef here.
}
); // Subscription is auto-cleared on destroy.
}
}
```
> Noticed `list` is `public` and `readonly`? That is because we will use `ListService` properties directly in the component's template. That may be considered as an anti-pattern, but it is much quicker to implement. You can always use public component properties instead.
Place `ListService` properties into the template like this:
```html
<abp-table
[value]="book.items"
[(page)]="list.page"
[rows]="list.maxResultCount"
[totalRecords]="book.totalCount"
[headerTemplate]="tableHeader"
[bodyTemplate]="tableBody"
[abpLoading]="list.isLoading$ | async"
>
</abp-table>
<ng-template #tableHeader>
<tr>
<th (click)="nameSort.sort('name')">
{%{{{ '::Name' | abpLocalization }}}%}
<abp-sort-order-icon
#nameSort
sortKey="name"
[(selectedSortKey)]="list.sortKey"
[(order)]="list.sortOrder"
></abp-sort-order-icon>
</th>
</tr>
</ng-template>
<ng-template #tableBody let-data>
<tr>
<td>{%{{{ data.name }}}%}</td>
</tr>
</ng-template>
```
## Usage with Observables
You may use observables in combination with [AsyncPipe](https://angular.io/guide/observables-in-angular#async-pipe) of Angular instead. Here are some possibilities:
```ts
book$ = this.list.hookToQuery(query => this.bookService.getListByInput(query));
```
```html
<!-- simplified representation of the template -->
<abp-table
[value]="(book$ | async)?.items || []"
[totalRecords]="(book$ | async)?.totalCount"
>
</abp-table>
<!-- DO NOT WORRY, ONLY ONE REQUEST WILL BE MADE -->
```
...or...
```ts
@Select(BookState.getBooks)
books$: Observable<BookDto[]>;
@Select(BookState.getBookCount)
bookCount$: Observable<number>;
ngOnInit() {
this.list.hookToQuery((query) => this.store.dispatch(new GetBooks(query))).subscribe();
}
```
```html
<!-- simplified representation of the template -->
<abp-table
[value]="books$ | async"
[totalRecords]="bookCount$ | async"
>
</abp-table>
```
## How to Refresh Table on Create/Update/Delete
`ListService` exposes a `get` method to trigger a request with the current query. So, basically, whenever a create, update, or delete action resolves, you can call `this.list.get();` and it will call hooked stream creator again.
```ts
this.store.dispatch(new DeleteBook(id)).subscribe(this.list.get);
```
...or...
```ts
this.bookService.createByInput(form.value)
.subscribe(() => {
this.list.get();
// Other subscription logic here
})
```
## How to Implement Server-Side Search in a Table
`ListService` exposes a `filter` property that will trigger a request with the current query and the given search string. All you need to do is to bind it to an input element with two-way binding.
```html
<!-- simplified representation -->
<input type="text" name="search" [(ngModel)]="list.filter">
```

6
docs/en/UI/Angular/Track-By-Service.md

@ -111,3 +111,9 @@ class DemoComponent {
trackByTenantAccountId = trackByDeep<Item>('tenant', 'account', 'id');
}
```
## What's Next?
- [ListService](./List-Service.md)

4
docs/en/docs-nav.json

@ -383,6 +383,10 @@
{
"text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md"
},
{
"text": "ListService",
"path": "UI/Angular/List-Service.md"
}
]
},

1
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/EntityExtensionDto.cs

@ -9,6 +9,5 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
public Dictionary<string, ExtensionPropertyDto> Properties { get; set; }
public Dictionary<string, object> Configuration { get; set; }
}
}

13
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionEnumDto.cs

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
{
[Serializable]
public class ExtensionEnumDto
{
public List<ExtensionEnumFieldDto> Fields { get; set; }
public string LocalizationResource { get; set; }
}
}

12
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionEnumFieldDto.cs

@ -0,0 +1,12 @@
using System;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
{
[Serializable]
public class ExtensionEnumFieldDto
{
public string Name { get; set; }
public object Value { get; set; }
}
}

25
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyAttributeDto.cs

@ -1,36 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
{
[Serializable]
public class ExtensionPropertyAttributeDto
{
public string Type { get; set; }
public string TypeSimple { get; set; }
public Dictionary<string, object> Configuration { get; set; }
public static ExtensionPropertyAttributeDto Create(Attribute attribute)
{
var attributeType = attribute.GetType();
var dto = new ExtensionPropertyAttributeDto
{
Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(attributeType),
TypeSimple = TypeHelper.GetSimplifiedName(attributeType),
Configuration = new Dictionary<string, object>()
};
if (attribute is StringLengthAttribute stringLengthAttribute)
{
dto.Configuration["MaximumLength"] = stringLengthAttribute.MaximumLength;
dto.Configuration["MinimumLength"] = stringLengthAttribute.MinimumLength;
}
//TODO: Others!
return dto;
}
public Dictionary<string, object> Config { get; set; }
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyDto.cs

@ -21,5 +21,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
public List<ExtensionPropertyAttributeDto> Attributes { get; set; }
public Dictionary<string, object> Configuration { get; set; }
public object DefaultValue { get; set; }
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ObjectExtensionsDto.cs

@ -7,5 +7,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
public class ObjectExtensionsDto
{
public Dictionary<string, ModuleExtensionDto> Modules { get; set; }
public Dictionary<string, ExtensionEnumDto> Enums { get; set; }
}
}

38
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperLocalizer.cs

@ -21,38 +21,30 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers
public string GetLocalizedText(string text, ModelExplorer explorer)
{
var resourceType = GetResourceTypeFromModelExplorer(explorer);
var localizer = GetStringLocalizer(resourceType);
return localizer == null ? text : localizer[text].Value;
}
public IStringLocalizer GetLocalizer(ModelExplorer explorer)
{
var resourceType = GetResourceTypeFromModelExplorer(explorer);
return GetStringLocalizer(resourceType);
}
public IStringLocalizer GetLocalizer(Assembly assembly)
{
var resourceType = _options.AssemblyResources.GetOrDefault(assembly);
return GetStringLocalizer(resourceType);
var localizer = GetLocalizerOrNull(explorer);
return localizer == null
? text
: localizer[text].Value;
}
public IStringLocalizer GetLocalizer(Type resourceType)
public IStringLocalizer GetLocalizerOrNull(ModelExplorer explorer)
{
return GetStringLocalizer(resourceType);
return GetLocalizerOrNull(explorer.Container.ModelType.Assembly);
}
private IStringLocalizer GetStringLocalizer(Type resourceType)
public IStringLocalizer GetLocalizerOrNull(Assembly assembly)
{
return resourceType == null ? null : _stringLocalizerFactory.Create(resourceType);
var resourceType = GetResourceType(assembly);
return resourceType == null
? _stringLocalizerFactory.CreateDefaultOrNull()
: _stringLocalizerFactory.Create(resourceType);
}
private Type GetResourceTypeFromModelExplorer(ModelExplorer explorer)
private Type GetResourceType(Assembly assembly)
{
var assembly = explorer.Container.ModelType.Assembly;
return _options.AssemblyResources.GetOrDefault(assembly);
return _options
.AssemblyResources
.GetOrDefault(assembly);
}
}
}

21
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperExtensions.cs

@ -2,20 +2,31 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Text.Encodings.Web;
using Volo.Abp.Threading;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions
{
public static class TagHelperExtensions
{
public static async Task<TagHelperOutput> ProcessAndGetOutputAsync(this TagHelper tagHelper, TagHelperAttributeList attributeList, TagHelperContext context, string tagName = "div", TagMode tagMode = TagMode.SelfClosing)
public static async Task<TagHelperOutput> ProcessAndGetOutputAsync(
this TagHelper tagHelper,
TagHelperAttributeList attributeList,
TagHelperContext context,
string tagName = "div",
TagMode tagMode = TagMode.SelfClosing)
{
var innerOutput = new TagHelperOutput(tagName, attributeList, (useCachedResult, encoder) => Task.Run<TagHelperContent>(() => new DefaultTagHelperContent()))
var innerOutput = new TagHelperOutput(
tagName,
attributeList,
(useCachedResult, encoder) => Task.Run<TagHelperContent>(() => new DefaultTagHelperContent()))
{
TagMode = tagMode
};
var innerContext = new TagHelperContext(attributeList, context.Items, Guid.NewGuid().ToString());
var innerContext = new TagHelperContext(
attributeList,
context.Items,
Guid.NewGuid().ToString()
);
tagHelper.Init(context);

7
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs

@ -33,6 +33,13 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
[ViewContext]
public ViewContext ViewContext { get; set; }
[HtmlAttributeName("asp-format")]
public string Format { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public AbpInputTagHelper(AbpInputTagHelperService tagHelperService)
: base(tagHelperService)
{

56
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs

@ -91,7 +91,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
inputHtml + label :
label + inputHtml;
return innerContent + infoHtml + validation;
return innerContent + infoHtml + validation;
}
protected virtual string SurroundInnerHtmlAndGet(TagHelperContext context, TagHelperOutput output, string innerHtml, bool isCheckbox)
@ -103,30 +103,56 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual TagHelper GetInputTagHelper(TagHelperContext context, TagHelperOutput output)
{
var textAreaAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<TextArea>();
if (textAreaAttribute != null)
if (TagHelper.AspFor.ModelExplorer.GetAttribute<TextArea>() != null)
{
return new TextAreaTagHelper(_generator)
var textAreaTagHelper = new TextAreaTagHelper(_generator)
{
For = TagHelper.AspFor,
ViewContext = TagHelper.ViewContext
};
if (!TagHelper.Name.IsNullOrEmpty())
{
textAreaTagHelper.Name = TagHelper.Name;
}
return textAreaTagHelper;
}
return new InputTagHelper(_generator)
var inputTagHelper = new InputTagHelper(_generator)
{
For = TagHelper.AspFor,
InputTypeName = TagHelper.InputTypeName,
ViewContext = TagHelper.ViewContext
};
if (!TagHelper.Format.IsNullOrEmpty())
{
inputTagHelper.Format = TagHelper.Format;
}
if (!TagHelper.Name.IsNullOrEmpty())
{
inputTagHelper.Name = TagHelper.Name;
}
if (!TagHelper.Value.IsNullOrEmpty())
{
inputTagHelper.Value = TagHelper.Value;
}
return inputTagHelper;
}
protected virtual async Task<(TagHelperOutput, bool)> GetInputTagHelperOutputAsync(TagHelperContext context, TagHelperOutput output)
{
var tagHelper = GetInputTagHelper(context, output);
var inputTagHelperOutput = await tagHelper.ProcessAndGetOutputAsync(GetInputAttributes(context, output), context, "input");
var inputTagHelperOutput = await tagHelper.ProcessAndGetOutputAsync(
GetInputAttributes(context, output),
context,
"input"
);
ConvertToTextAreaIfTextArea(inputTagHelperOutput);
AddDisabledAttribute(inputTagHelperOutput);
@ -245,7 +271,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return "";
}
return TagHelper.AspFor.ModelExplorer.GetAttribute<RequiredAttribute>() != null ? "<span> * </span>":"";
return TagHelper.AspFor.ModelExplorer.GetAttribute<RequiredAttribute>() != null ? "<span> * </span>" : "";
}
protected virtual string GetInfoAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox)
@ -282,7 +308,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer);
return "<small id=\""+ idAttr?.Value + "InfoText\" class=\"form-text text-muted\">" +
return "<small id=\"" + idAttr?.Value + "InfoText\" class=\"form-text text-muted\">" +
localizedText +
"</small>";
}
@ -332,7 +358,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
var groupPrefix = "group-";
var tagHelperAttributes = output.Attributes.Where(a => !a.Name.StartsWith(groupPrefix)).ToList();
var attrList = new TagHelperAttributeList();
foreach (var tagHelperAttribute in tagHelperAttributes)
@ -345,6 +371,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
attrList.Add("type", TagHelper.InputTypeName);
}
if (!TagHelper.Name.IsNullOrEmpty() && !attrList.ContainsName("name"))
{
attrList.Add("name", TagHelper.Name);
}
if (!TagHelper.Value.IsNullOrEmpty() && !attrList.ContainsName("value"))
{
attrList.Add("value", TagHelper.Value);
}
return attrList;
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs

@ -90,7 +90,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual List<SelectListItem> GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer)
{
var localizer = _tagHelperLocalizer.GetLocalizer(explorer);
var localizer = _tagHelperLocalizer.GetLocalizerOrNull(explorer);
var selectItems = explorer.Metadata.IsEnum ? explorer.ModelType.GetTypeInfo().GetMembers(BindingFlags.Public | BindingFlags.Static)
.Select((t, i) => new SelectListItem { Value = i.ToString(), Text = GetLocalizedPropertyName(localizer, explorer.ModelType, t.Name) }).ToList() : null;

68
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Linq.Dynamic.Core;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
@ -12,6 +12,9 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Localization;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
@ -20,12 +23,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
private readonly IHtmlGenerator _generator;
private readonly HtmlEncoder _encoder;
private readonly IAbpTagHelperLocalizer _tagHelperLocalizer;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
public AbpSelectTagHelperService(IHtmlGenerator generator, HtmlEncoder encoder, IAbpTagHelperLocalizer tagHelperLocalizer)
public AbpSelectTagHelperService(
IHtmlGenerator generator,
HtmlEncoder encoder,
IAbpTagHelperLocalizer tagHelperLocalizer,
IStringLocalizerFactory stringLocalizerFactory)
{
_generator = generator;
_encoder = encoder;
_tagHelperLocalizer = tagHelperLocalizer;
_stringLocalizerFactory = stringLocalizerFactory;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -102,7 +111,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return TagHelper.AspItems.ToList();
}
if (TagHelper.AspFor.ModelExplorer.Metadata.IsEnum)
if (IsEnum())
{
return GetSelectItemsFromEnum(context, output, TagHelper.AspFor.ModelExplorer);
}
@ -116,6 +125,17 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
throw new Exception("No items provided for select attribute.");
}
private bool IsEnum()
{
var value = TagHelper.AspFor.Model;
if (value != null && value.GetType().IsEnum)
{
return true;
}
return TagHelper.AspFor.ModelExplorer.Metadata.IsEnum;
}
protected virtual async Task<string> GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput selectTag)
{
if (!string.IsNullOrEmpty(TagHelper.Label))
@ -186,8 +206,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual List<SelectListItem> GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer)
{
var localizer = _tagHelperLocalizer.GetLocalizer(explorer);
var selectItems = new List<SelectListItem>();
var isNullableType = Nullable.GetUnderlyingType(explorer.ModelType) != null;
var enumType = explorer.ModelType;
@ -198,26 +216,34 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
selectItems.Add(new SelectListItem());
}
selectItems.AddRange(enumType.GetEnumNames()
.Select(enumName => new SelectListItem
{
Value = Convert.ToUInt64(Enum.Parse(enumType, enumName)).ToString(),
Text = GetLocalizedPropertyName(localizer, enumType, enumName)
}));
return selectItems;
}
var containerLocalizer = _tagHelperLocalizer.GetLocalizerOrNull(explorer.Container.ModelType.Assembly);
protected virtual string GetLocalizedPropertyName(IStringLocalizer localizer, Type enumType, string propertyName)
{
if (localizer == null)
foreach (var enumValue in enumType.GetEnumValues())
{
return propertyName;
var memberName = enumType.GetEnumName(enumValue);
var localizedMemberName = AbpInternalLocalizationHelper.LocalizeWithFallback(
new[]
{
containerLocalizer,
_stringLocalizerFactory.CreateDefaultOrNull()
},
new[]
{
$"Enum:{enumType.Name}.{memberName}",
$"{enumType.Name}.{memberName}",
memberName
},
memberName
);
selectItems.Add(new SelectListItem
{
Value = enumValue.ToString(),
Text = localizedMemberName
});
}
var localizedString = localizer[enumType.Name + "." + propertyName];
return !localizedString.ResourceNotFound ? localizedString.Value : localizer[propertyName].Value;
return selectItems;
}
protected virtual List<SelectListItem> GetSelectItemsFromAttribute(

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/IAbpTagHelperLocalizer.cs

@ -10,10 +10,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers
{
string GetLocalizedText(string text, ModelExplorer explorer);
IStringLocalizer GetLocalizer(ModelExplorer explorer);
IStringLocalizer GetLocalizerOrNull(ModelExplorer explorer);
IStringLocalizer GetLocalizer(Assembly assembly);
IStringLocalizer GetLocalizer(Type resourceType);
IStringLocalizer GetLocalizerOrNull(Assembly assembly);
}
}

13
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs

@ -7,6 +7,7 @@ using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
@ -17,12 +18,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
private readonly IHtmlGenerator _generator;
private readonly HtmlEncoder _encoder;
private readonly IAbpTagHelperLocalizer _tagHelperLocalizer;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
public AbpPaginationTagHelperService(IHtmlGenerator generator, HtmlEncoder encoder, IAbpTagHelperLocalizer tagHelperLocalizer)
public AbpPaginationTagHelperService(
IHtmlGenerator generator,
HtmlEncoder encoder,
IAbpTagHelperLocalizer tagHelperLocalizer,
IStringLocalizerFactory stringLocalizerFactory)
{
_generator = generator;
_encoder = encoder;
_tagHelperLocalizer = tagHelperLocalizer;
_stringLocalizerFactory = stringLocalizerFactory;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -119,7 +126,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
protected virtual async Task<string> RenderAnchorTagHelperLinkHtmlAsync(TagHelperContext context, TagHelperOutput output, string currentPage, string localizationKey)
{
var localizer = _tagHelperLocalizer.GetLocalizer(typeof(AbpUiResource));
var localizer = _stringLocalizerFactory.Create(typeof(AbpUiResource));
var anchorTagHelper = GetAnchorTagHelper(currentPage, out var attributeList);
@ -156,7 +163,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
protected virtual string GetOpeningTags(TagHelperContext context, TagHelperOutput output)
{
var localizer = _tagHelperLocalizer.GetLocalizer(typeof(AbpUiResource));
var localizer = _stringLocalizerFactory.Create(typeof(AbpUiResource));
var pagerInfo = (TagHelper.ShowInfo ?? false) ?
" <div class=\"col-sm-12 col-md-5\"> " + localizer["PagerInfo{0}{1}{2}", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount] + "</div>\r\n"

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs

@ -4,7 +4,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker
{
[DependsOn(typeof(JQueryScriptContributor))]
public class BootstrapDatepickerScriptContributor : BundleContributor

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerStyleContributor.cs

@ -1,7 +1,7 @@
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker
{
public class BootstrapDatepickerStyleContributor : BundleContributor
{

16
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JQueryFormScriptContributor.cs

@ -0,0 +1,16 @@
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JsTree
{
[DependsOn(typeof(JQueryScriptContributor))]
public class JsTreeScriptContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/jstree/jstree.min.js");
}
}
}

13
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JsTreeOptions.cs

@ -0,0 +1,13 @@
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JsTree
{
public class JsTreeOptions
{
/// <summary>
/// Path of the style file for the JsTree library.
/// Setting to null ignores the style file.
///
/// Default value: "/libs/jstree/themes/default/style.min.css".
/// </summary>
public string StylePath { get; set; } = "/libs/jstree/themes/default/style.min.css";
}
}

26
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JsTreeStyleContributor.cs

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JsTree
{
public class JsTreeStyleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
var options = context
.ServiceProvider
.GetRequiredService<IOptions<JsTreeOptions>>()
.Value;
if (options.StylePath.IsNullOrEmpty())
{
return;
}
context.Files.AddIfNotContains(options.StylePath);
}
}
}

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Luxon/LuxonScriptContributor.cs

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Luxon

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs

@ -1,5 +1,6 @@
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Bootstrap;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.DatatablesNetBs4;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryForm;

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs

@ -1,5 +1,6 @@
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Bootstrap;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Core;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.DatatablesNetBs4;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.FontAwesome;

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js

@ -95,8 +95,11 @@ $.validator.defaults.ignore = ''; //TODO: Would be better if we can apply only f
});
_$modal.on('shown.bs.modal', function () {
//focuses first element if it's a typeable input.
//focuses first element if it's a typeable input.
var $firstVisibleInput = _$modal.find('input:not([type=hidden]):first');
_onOpenCallbacks.triggerAll(_publicApi);
if ($firstVisibleInput.hasClass("datepicker")) {
return; //don't pop-up date pickers...
}
@ -107,7 +110,6 @@ $.validator.defaults.ignore = ''; //TODO: Would be better if we can apply only f
}
$firstVisibleInput.focus();
_onOpenCallbacks.triggerAll(_publicApi);
});
var modalClass = abp.modals[options.modalClass];

30
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js

@ -8,7 +8,7 @@
};
/************************************************************************
* RECORD-ACTIONS extension for datatables *
* RECORD-ACTIONS extension for datatables *
*************************************************************************/
(function () {
if (!$.fn.dataTableExt) {
@ -328,4 +328,32 @@
})();
/************************************************************************
* Default Renderers *
*************************************************************************/
datatables.defaultRenderers = datatables.defaultRenderers || {};
datatables.defaultRenderers['boolean'] = function(value) {
if (value) {
return '<i class="fa fa-check"></i>';
} else {
return '<i class="fa fa-times"></i>';
}
};
datatables.defaultRenderers['date'] = function (value) {
return luxon
.DateTime
.fromISO(value, { locale: abp.localization.currentCulture.name })
.toLocaleString();
};
datatables.defaultRenderers['datetime'] = function (value) {
return luxon
.DateTime
.fromISO(value, { locale: abp.localization.currentCulture.name })
.toLocaleString(luxon.DateTime.DATETIME_SHORT);
};
})(jQuery);

100
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js

@ -96,9 +96,29 @@
get: _get
};
})();
function initializeObjectExtensions() {
var getShortEnumTypeName = function (enumType) {
var lastDotIndex = enumType.lastIndexOf('.');
if (lastDotIndex < 0) {
return enumType;
}
return enumType.substr(lastDotIndex + 1);
};
var getEnumMemberName = function (enumInfo, enumMemberValue) {
for (var i = 0; i < enumInfo.fields.length; i++) {
var enumField = enumInfo.fields[i];
if (enumField.value == enumMemberValue) {
return enumField.name;
}
}
return null;
};
function localizeDisplayName(propertyName, displayName) {
if (displayName && displayName.name) {
return abp.localization.localize(displayName.name, displayName.resource);
@ -111,6 +131,47 @@
return abp.localization.localize(propertyName);
}
function localizeWithFallback(localizationResources, keys, defaultValue) {
for (var i = 0; i < localizationResources.length; i++) {
var localizationResource = localizationResources[i];
if (!localizationResource) {
continue;
}
for (var j = 0; j < keys.length; j++) {
var key = keys[j];
if (abp.localization.isLocalized(key, localizationResource)) {
return abp.localization.localize(key, localizationResource);
}
}
}
return defaultValue;
}
function localizeEnumMember(property, enumMemberValue) {
var enumType = property.config.type;
var enumInfo = abp.objectExtensions.enums[enumType];
var enumMemberName = getEnumMemberName(enumInfo, enumMemberValue);
if (!enumMemberName) {
return enumMemberValue;
}
var shortEnumType = getShortEnumTypeName(enumType);
return localizeWithFallback(
[enumInfo.localizationResource, abp.localization.defaultResourceName],
[
'Enum:' + shortEnumType + '.' + enumMemberName,
shortEnumType + '.' + enumMemberName,
enumMemberName
],
enumMemberName
);
}
function configureTableColumns(tableName, columnConfigs) {
abp.ui.extensions.tableColumns.get(tableName)
.addContributor(
@ -137,16 +198,41 @@
return tableProperties;
}
function getValueFromRow(property, row) {
return row.extraProperties[property.name];;
}
function convertPropertyToColumnConfig(property) {
var columnConfig = {
title: localizeDisplayName(property.name, property.config.displayName),
data: "extraProperties." + property.name,
orderable: false
};
if (property.config.typeSimple === 'enum') {
columnConfig.render = function(data, type, row) {
var value = getValueFromRow(property, row);
return localizeEnumMember(property, value);
}
} else {
var defaultRenderer = abp.libs.datatables.defaultRenderers[property.config.typeSimple];
if (defaultRenderer) {
columnConfig.render = function (data, type, row) {
var value = getValueFromRow(property, row);
return defaultRenderer(value);
}
}
}
return columnConfig;
}
function convertPropertiesToColumnConfigs(properties) {
var columnConfigs = [];
for (var i = 0; i < properties.length; i++) {
var tableProperty = properties[i];
columnConfigs.push({
title: localizeDisplayName(tableProperty.name, tableProperty.config.displayName),
data: "extraProperties." + tableProperty.name,
orderable: false
});
columnConfigs.push(convertPropertyToColumnConfig(properties[i]));
}
return columnConfigs;

25
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.Alerts;
using Volo.Abp.AspNetCore.Mvc.Validation;
using Volo.Abp.Guids;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
@ -90,17 +91,13 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
{
if (_localizer == null)
{
if (LocalizationResourceType == null)
{
throw new AbpException($"{nameof(LocalizationResourceType)} should be set before using the {nameof(L)} object!");
}
_localizer = StringLocalizerFactory.Create(LocalizationResourceType);
_localizer = CreateLocalizer();
}
return _localizer;
}
}
private IStringLocalizer _localizer;
protected Type LocalizationResourceType { get; set; }
@ -152,5 +149,21 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
TempData = TempData
};
}
protected virtual IStringLocalizer CreateLocalizer()
{
if (LocalizationResourceType != null)
{
return StringLocalizerFactory.Create(LocalizationResourceType);
}
var localizer = StringLocalizerFactory.CreateDefaultOrNull();
if (localizer == null)
{
throw new AbpException($"Set {nameof(LocalizationResourceType)} or define the default localization resource type (by configuring the {nameof(AbpLocalizationOptions)}.{nameof(AbpLocalizationOptions.DefaultResourceType)}) to be able to use the {nameof(L)} object!");
}
return localizer;
}
}
}

150
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending
{
public static class MvcUiObjectExtensionPropertyInfoExtensions
{
private static readonly HashSet<Type> NumberTypes = new HashSet<Type> {
typeof(int),
typeof(long),
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
typeof(int?),
typeof(long?),
typeof(byte?),
typeof(sbyte?),
typeof(short?),
typeof(ushort?),
typeof(uint?),
typeof(long?),
typeof(ulong?),
typeof(float?),
typeof(double?),
typeof(decimal?)
};
public static string GetInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property)
{
if (property.IsDate())
{
return "{0:yyyy-MM-dd}";
}
if (property.IsDateTime())
{
return "{0:yyyy-MM-ddTHH:mm}";
}
return null;
}
public static string GetInputValueOrNull(this IBasicObjectExtensionPropertyInfo property, object value)
{
if (value == null)
{
return null;
}
if (TypeHelper.IsFloatingType(property.Type))
{
return value.ToString()?.Replace(',', '.');
}
/* Let the ASP.NET Core handle it! */
return null;
}
public static string GetInputType(this ObjectExtensionPropertyInfo propertyInfo)
{
foreach (var attribute in propertyInfo.Attributes)
{
var inputTypeByAttribute = GetInputTypeFromAttributeOrNull(attribute);
if (inputTypeByAttribute != null)
{
return inputTypeByAttribute;
}
}
return GetInputTypeFromTypeOrNull(propertyInfo.Type)
?? "text"; //default
}
private static string GetInputTypeFromAttributeOrNull(Attribute attribute)
{
if (attribute is EmailAddressAttribute)
{
return "email";
}
if (attribute is UrlAttribute)
{
return "url";
}
if (attribute is HiddenInputAttribute)
{
return "hidden";
}
if (attribute is PhoneAttribute)
{
return "tel";
}
if (attribute is DataTypeAttribute dataTypeAttribute)
{
switch (dataTypeAttribute.DataType)
{
case DataType.Password:
return "password";
case DataType.Date:
return "date";
case DataType.Time:
return "time";
case DataType.EmailAddress:
return "email";
case DataType.Url:
return "url";
case DataType.PhoneNumber:
return "tel";
case DataType.DateTime:
return "datetime-local";
}
}
return null;
}
private static string GetInputTypeFromTypeOrNull(Type type)
{
if (type == typeof(bool))
{
return "checkbox";
}
if (type == typeof(DateTime))
{
return "datetime-local";
}
if (NumberTypes.Contains(type))
{
return "number";
}
return null;
}
}
}

29
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs

@ -101,7 +101,18 @@ namespace Volo.Abp.AspNetCore.Mvc
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L => _localizer ?? (_localizer = StringLocalizerFactory.Create(LocalizationResource));
public IStringLocalizer L
{
get
{
if (_localizer == null)
{
_localizer = CreateLocalizer();
}
return _localizer;
}
}
private IStringLocalizer _localizer;
protected Type LocalizationResource
@ -121,5 +132,21 @@ namespace Volo.Abp.AspNetCore.Mvc
{
ModelValidator?.Validate(ModelState);
}
protected virtual IStringLocalizer CreateLocalizer()
{
if (LocalizationResource != null)
{
return StringLocalizerFactory.Create(LocalizationResource);
}
var localizer = StringLocalizerFactory.CreateDefaultOrNull();
if (localizer == null)
{
throw new AbpException($"Set {nameof(LocalizationResource)} or define the default localization resource type (by configuring the {nameof(AbpLocalizationOptions)}.{nameof(AbpLocalizationOptions.DefaultResourceType)}) to be able to use the {nameof(L)} object!");
}
return localizer;
}
}
}

90
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs → framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs

@ -10,30 +10,37 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
{
public class CachedObjectExtensionsDtoService : ICachedObjectExtensionsDtoService, ISingletonDependency
{
private volatile ObjectExtensionsDto _cachedValue;
private readonly object _syncLock = new object();
protected IExtensionPropertyAttributeDtoFactory ExtensionPropertyAttributeDtoFactory { get; }
protected volatile ObjectExtensionsDto CachedValue;
protected readonly object SyncLock = new object();
public CachedObjectExtensionsDtoService(IExtensionPropertyAttributeDtoFactory extensionPropertyAttributeDtoFactory)
{
ExtensionPropertyAttributeDtoFactory = extensionPropertyAttributeDtoFactory;
}
public virtual ObjectExtensionsDto Get()
{
if (_cachedValue == null)
if (CachedValue == null)
{
lock (_syncLock)
lock (SyncLock)
{
if (_cachedValue == null)
if (CachedValue == null)
{
_cachedValue = GenerateCacheValue();
CachedValue = GenerateCacheValue();
}
}
}
return _cachedValue;
return CachedValue;
}
protected virtual ObjectExtensionsDto GenerateCacheValue()
{
var objectExtensionsDto = new ObjectExtensionsDto
{
Modules = new Dictionary<string, ModuleExtensionDto>()
Modules = new Dictionary<string, ModuleExtensionDto>(),
Enums = new Dictionary<string, ExtensionEnumDto>()
};
foreach (var moduleConfig in ObjectExtensionManager.Instance.Modules())
@ -41,6 +48,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
objectExtensionsDto.Modules[moduleConfig.Key] = CreateModuleExtensionDto(moduleConfig.Value);
}
FillEnums(objectExtensionsDto);
return objectExtensionsDto;
}
@ -98,10 +107,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
var extensionPropertyDto = new ExtensionPropertyDto
{
Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyConfig.Type),
TypeSimple = TypeHelper.GetSimplifiedName(propertyConfig.Type),
TypeSimple = GetSimpleTypeName(propertyConfig),
Attributes = new List<ExtensionPropertyAttributeDto>(),
DisplayName = CreateDisplayNameDto(propertyConfig),
Configuration = new Dictionary<string, object>(),
DefaultValue = propertyConfig.GetDefaultValue(),
Api = new ExtensionPropertyApiDto
{
OnGet = new ExtensionPropertyApiGetDto
@ -137,7 +147,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
foreach (var attribute in propertyConfig.Attributes)
{
extensionPropertyDto.Attributes.Add(
ExtensionPropertyAttributeDto.Create(attribute)
ExtensionPropertyAttributeDtoFactory.Create(attribute)
);
}
@ -149,6 +159,26 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
return extensionPropertyDto;
}
protected virtual string GetSimpleTypeName(ExtensionPropertyConfiguration propertyConfig)
{
if (propertyConfig.Type.IsEnum)
{
return "enum";
}
if (propertyConfig.IsDate())
{
return "date";
}
if (propertyConfig.IsDateTime())
{
return "datetime";
}
return TypeHelper.GetSimplifiedName(propertyConfig.Type);
}
protected virtual LocalizableStringDto CreateDisplayNameDto(ExtensionPropertyConfiguration propertyConfig)
{
if (propertyConfig.DisplayName == null)
@ -174,5 +204,45 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
return null;
}
protected virtual void FillEnums(ObjectExtensionsDto objectExtensionsDto)
{
var enumProperties = ObjectExtensionManager.Instance.Modules().Values
.SelectMany(
m => m.Entities.Values.SelectMany(
e => e.GetProperties()
)
)
.Where(p => p.Type.IsEnum)
.ToList();
foreach (var enumProperty in enumProperties)
{
// ReSharper disable once AssignNullToNotNullAttribute (enumProperty.Type.FullName can not be null for this case)
objectExtensionsDto.Enums[enumProperty.Type.FullName] = CreateExtensionEnumDto(enumProperty);
}
}
protected virtual ExtensionEnumDto CreateExtensionEnumDto(ExtensionPropertyConfiguration enumProperty)
{
var extensionEnumDto = new ExtensionEnumDto
{
Fields = new List<ExtensionEnumFieldDto>(),
LocalizationResource = enumProperty.GetLocalizationResourceNameOrNull()
};
foreach (var enumValue in enumProperty.Type.GetEnumValues())
{
extensionEnumDto.Fields.Add(
new ExtensionEnumFieldDto
{
Name = enumProperty.Type.GetEnumName(enumValue),
Value = enumValue
}
);
}
return extensionEnumDto;
}
}
}

95
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyAttributeDtoFactory.cs

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
{
public class ExtensionPropertyAttributeDtoFactory : IExtensionPropertyAttributeDtoFactory, ITransientDependency
{
public virtual ExtensionPropertyAttributeDto Create(Attribute attribute)
{
return new ExtensionPropertyAttributeDto
{
TypeSimple = GetSimplifiedName(attribute),
Config = CreateConfiguration(attribute)
};
}
protected virtual string GetSimplifiedName(Attribute attribute)
{
return attribute.GetType().Name.ToCamelCase().RemovePostFix("Attribute");
}
protected virtual Dictionary<string, object> CreateConfiguration(Attribute attribute)
{
var configuration = new Dictionary<string, object>();
AddPropertiesToConfiguration(attribute, configuration);
return configuration;
}
protected virtual void AddPropertiesToConfiguration(Attribute attribute, Dictionary<string, object> configuration)
{
var properties = attribute
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties)
{
if (IgnoreProperty(attribute, property))
{
continue;
}
var value = GetPropertyValue(attribute, property);
if (value == null)
{
continue;
}
configuration[property.Name.ToCamelCase()] = value;
}
}
protected virtual bool IgnoreProperty(Attribute attribute, PropertyInfo property)
{
if (property.DeclaringType == null ||
property.DeclaringType.IsIn(typeof(ValidationAttribute), typeof(Attribute), typeof(object)))
{
return true;
}
if (property.PropertyType == typeof(DisplayFormatAttribute))
{
return true;
}
return false;
}
protected virtual object GetPropertyValue(Attribute attribute, PropertyInfo property)
{
var value = property.GetValue(attribute);
if (value == null)
{
return null;
}
if (property.PropertyType.IsEnum)
{
return Enum.GetName(property.PropertyType, value);
}
if (property.PropertyType == typeof(Type))
{
return TypeHelper.GetSimplifiedName((Type) value);
}
return value;
}
}
}

0
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ICachedObjectExtensionsDtoService.cs → framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ICachedObjectExtensionsDtoService.cs

9
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/IExtensionPropertyAttributeDtoFactory.cs

@ -0,0 +1,9 @@
using System;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
{
public interface IExtensionPropertyAttributeDtoFactory
{
ExtensionPropertyAttributeDto Create(Attribute attribute);
}
}

1
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertiesDictionaryModelBinderProvider.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;

6
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
{
@ -59,7 +60,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
return value;
}
return Convert.ChangeType(value, propertyInfo.Type);
return TypeHelper.ConvertFromString(
propertyInfo.Type,
value
);
}
}
}

97
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/ObjectExtending/ObjectExtendingPropertyInfoExtensions.cs

@ -1,98 +1,35 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace Volo.Abp.ObjectExtending
{
public static class ObjectExtensionPropertyInfoAspNetCoreMvcExtensions
{
public static string GetInputType(this ObjectExtensionPropertyInfo propertyInfo)
private static readonly Type[] DateTimeTypes =
{
foreach (var attribute in propertyInfo.Attributes)
{
var inputTypeByAttribute = GetInputTypeFromAttributeOrNull(attribute);
if (inputTypeByAttribute != null)
{
return inputTypeByAttribute;
}
}
typeof(DateTime),
typeof(DateTimeOffset)
};
return GetInputTypeFromTypeOrNull(propertyInfo.Type)
?? "text"; //default
public static bool IsDate(this IBasicObjectExtensionPropertyInfo property)
{
return DateTimeTypes.Contains(property.Type) &&
property.GetDataTypeOrNull() == DataType.Date;
}
private static string GetInputTypeFromAttributeOrNull(Attribute attribute)
public static bool IsDateTime(this IBasicObjectExtensionPropertyInfo property)
{
if (attribute is EmailAddressAttribute)
{
return "email";
}
if (attribute is UrlAttribute)
{
return "url";
}
if (attribute is HiddenInputAttribute)
{
return "hidden";
}
if (attribute is PhoneAttribute)
{
return "tel";
}
if (attribute is DataTypeAttribute dataTypeAttribute)
{
switch (dataTypeAttribute.DataType)
{
case DataType.Password:
return "password";
case DataType.Date:
return "date";
case DataType.Time:
return "time";
case DataType.EmailAddress:
return "email";
case DataType.Url:
return "url";
case DataType.PhoneNumber:
return "tel";
case DataType.DateTime:
return "datetime-local";
}
}
return null;
return DateTimeTypes.Contains(property.Type) &&
!property.IsDate();
}
private static string GetInputTypeFromTypeOrNull(Type type)
public static DataType? GetDataTypeOrNull(this IBasicObjectExtensionPropertyInfo property)
{
if (type == typeof(bool))
{
return "checkbox";
}
if (type == typeof(DateTime))
{
return "datetime-local";
}
if (type == typeof(int) ||
type == typeof(long) ||
type == typeof(byte) ||
type == typeof(sbyte) ||
type == typeof(short) ||
type == typeof(ushort) ||
type == typeof(uint) ||
type == typeof(long) ||
type == typeof(ulong))
{
return "number";
}
return null;
return property
.Attributes
.OfType<DataTypeAttribute>()
.FirstOrDefault()?.DataType;
}
}
}

58
framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpHub.cs

@ -57,7 +57,18 @@ namespace Volo.Abp.AspNetCore.SignalR
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L => _localizer ?? (_localizer = StringLocalizerFactory.Create(LocalizationResource));
public IStringLocalizer L
{
get
{
if (_localizer == null)
{
_localizer = CreateLocalizer();
}
return _localizer;
}
}
private IStringLocalizer _localizer;
protected Type LocalizationResource
@ -70,6 +81,22 @@ namespace Volo.Abp.AspNetCore.SignalR
}
}
private Type _localizationResource = typeof(DefaultResource);
protected virtual IStringLocalizer CreateLocalizer()
{
if (LocalizationResource != null)
{
return StringLocalizerFactory.Create(LocalizationResource);
}
var localizer = StringLocalizerFactory.CreateDefaultOrNull();
if (localizer == null)
{
throw new AbpException($"Set {nameof(LocalizationResource)} or define the default localization resource type (by configuring the {nameof(AbpLocalizationOptions)}.{nameof(AbpLocalizationOptions.DefaultResourceType)}) to be able to use the {nameof(L)} object!");
}
return localizer;
}
}
public abstract class AbpHub<T> : Hub<T>
@ -118,7 +145,18 @@ namespace Volo.Abp.AspNetCore.SignalR
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L => _localizer ?? (_localizer = StringLocalizerFactory.Create(LocalizationResource));
public IStringLocalizer L
{
get
{
if (_localizer == null)
{
_localizer = CreateLocalizer();
}
return _localizer;
}
}
private IStringLocalizer _localizer;
protected Type LocalizationResource
@ -131,5 +169,21 @@ namespace Volo.Abp.AspNetCore.SignalR
}
}
private Type _localizationResource = typeof(DefaultResource);
protected virtual IStringLocalizer CreateLocalizer()
{
if (LocalizationResource != null)
{
return StringLocalizerFactory.Create(LocalizationResource);
}
var localizer = StringLocalizerFactory.CreateDefaultOrNull();
if (localizer == null)
{
throw new AbpException($"Set {nameof(LocalizationResource)} or define the default localization resource type (by configuring the {nameof(AbpLocalizationOptions)}.{nameof(AbpLocalizationOptions.DefaultResourceType)}) to be able to use the {nameof(L)} object!");
}
return localizer;
}
}
}

19
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs

@ -36,7 +36,7 @@ namespace Volo.Abp.Auditing
IClock clock,
IAuditingStore auditingStore,
ILogger<AuditingHelper> logger,
IServiceProvider serviceProvider,
IServiceProvider serviceProvider,
ICorrelationIdProvider correlationIdProvider)
{
Options = options.Value;
@ -77,9 +77,10 @@ namespace Volo.Abp.Auditing
var classType = methodInfo.DeclaringType;
if (classType != null)
{
if (AuditingInterceptorRegistrar.ShouldAuditTypeByDefault(classType))
var shouldAudit = AuditingInterceptorRegistrar.ShouldAuditTypeByDefaultOrNull(classType);
if (shouldAudit != null)
{
return true;
return shouldAudit.Value;
}
}
@ -123,7 +124,7 @@ namespace Volo.Abp.Auditing
return defaultValue;
}
public virtual AuditLogInfo CreateAuditLogInfo()
{
var auditInfo = new AuditLogInfo
@ -147,8 +148,8 @@ namespace Volo.Abp.Auditing
public virtual AuditLogActionInfo CreateAuditLogAction(
AuditLogInfo auditLog,
Type type,
MethodInfo method,
Type type,
MethodInfo method,
object[] arguments)
{
return CreateAuditLogAction(auditLog, type, method, CreateArgumentsDictionary(method, arguments));
@ -156,8 +157,8 @@ namespace Volo.Abp.Auditing
public virtual AuditLogActionInfo CreateAuditLogAction(
AuditLogInfo auditLog,
Type type,
MethodInfo method,
Type type,
MethodInfo method,
IDictionary<string, object> arguments)
{
var actionInfo = new AuditLogActionInfo
@ -240,4 +241,4 @@ namespace Volo.Abp.Auditing
return dictionary;
}
}
}
}

10
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs

@ -21,8 +21,8 @@ namespace Volo.Abp.Auditing
{
return false;
}
if (ShouldAuditTypeByDefault(type))
if (ShouldAuditTypeByDefaultOrNull(type) == true)
{
return true;
}
@ -36,7 +36,7 @@ namespace Volo.Abp.Auditing
}
//TODO: Move to a better place
public static bool ShouldAuditTypeByDefault(Type type)
public static bool? ShouldAuditTypeByDefaultOrNull(Type type)
{
//TODO: In an inheritance chain, it would be better to check the attributes on the top class first.
@ -55,7 +55,7 @@ namespace Volo.Abp.Auditing
return true;
}
return false;
return null;
}
}
}
}

46
framework/src/Volo.Abp.Core/Volo/Abp/AbpInitializationException.cs

@ -0,0 +1,46 @@
using System;
using System.Runtime.Serialization;
namespace Volo.Abp
{
public class AbpInitializationException : AbpException
{
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
public AbpInitializationException()
{
}
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
/// <param name="message">Exception message</param>
public AbpInitializationException(string message)
: base(message)
{
}
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception</param>
public AbpInitializationException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Constructor for serializing.
/// </summary>
public AbpInitializationException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{
}
}
}

27
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs

@ -105,7 +105,14 @@ namespace Volo.Abp.Modularity
//PreConfigureServices
foreach (var module in modules.Where(m => m.Instance is IPreConfigureServices))
{
((IPreConfigureServices)module.Instance).PreConfigureServices(context);
try
{
((IPreConfigureServices)module.Instance).PreConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IPreConfigureServices.PreConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
//ConfigureServices
@ -119,13 +126,27 @@ namespace Volo.Abp.Modularity
}
}
module.Instance.ConfigureServices(context);
try
{
module.Instance.ConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IAbpModule.ConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
//PostConfigureServices
foreach (var module in modules.Where(m => m.Instance is IPostConfigureServices))
{
((IPostConfigureServices)module.Instance).PostConfigureServices(context);
try
{
((IPostConfigureServices)module.Instance).PostConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IPostConfigureServices.PostConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
foreach (var module in modules)

22
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs

@ -34,11 +34,18 @@ namespace Volo.Abp.Modularity
{
LogListOfModules();
foreach (var Contributor in _lifecycleContributors)
foreach (var contributor in _lifecycleContributors)
{
foreach (var module in _moduleContainer.Modules)
{
Contributor.Initialize(context, module.Instance);
try
{
contributor.Initialize(context, module.Instance);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during the initialize {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
}
@ -59,11 +66,18 @@ namespace Volo.Abp.Modularity
{
var modules = _moduleContainer.Modules.Reverse().ToList();
foreach (var Contributor in _lifecycleContributors)
foreach (var contributor in _lifecycleContributors)
{
foreach (var module in modules)
{
Contributor.Shutdown(context, module.Instance);
try
{
contributor.Shutdown(context, module.Instance);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during the shutdown {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
}
}

97
framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs

@ -1,21 +1,55 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Volo.Abp.Localization;
namespace Volo.Abp.Reflection
{
public static class TypeHelper
{
private static readonly HashSet<Type> FloatingTypes = new HashSet<Type>
{
typeof(float),
typeof(double),
typeof(decimal)
};
private static readonly HashSet<Type> NonNullablePrimitiveTypes = new HashSet<Type>
{
typeof(byte),
typeof(short),
typeof(int),
typeof(long),
typeof(sbyte),
typeof(ushort),
typeof(uint),
typeof(ulong),
typeof(bool),
typeof(float),
typeof(decimal),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(Guid)
};
public static bool IsNonNullablePrimitiveType(Type type)
{
return NonNullablePrimitiveTypes.Contains(type);
}
public static bool IsFunc(object obj)
{
if (obj == null)
{
return false;
}
var type = obj.GetType();
if (!type.GetTypeInfo().IsGenericType)
{
@ -264,5 +298,66 @@ namespace Volo.Abp.Reflection
return type.FullName;
}
public static object ConvertFromString<TTargetType>(string value)
{
return ConvertFromString(typeof(TTargetType), value);
}
public static object ConvertFromString(Type targetType, string value)
{
if (value == null)
{
return null;
}
var converter = TypeDescriptor.GetConverter(targetType);
if (IsFloatingType(targetType))
{
using (CultureHelper.Use(CultureInfo.InvariantCulture))
{
return converter.ConvertFromString(value.Replace(',', '.'));
}
}
return converter.ConvertFromString(value);
}
public static bool IsFloatingType(Type type, bool includeNullable = true)
{
if (FloatingTypes.Contains(type))
{
return true;
}
if (includeNullable &&
IsNullable(type) &&
FloatingTypes.Contains(type.GenericTypeArguments[0]))
{
return true;
}
return false;
}
public static object ConvertFrom<TTargetType>(object value)
{
return ConvertFrom(typeof(TTargetType), value);
}
public static object ConvertFrom(Type targetType, object value)
{
return TypeDescriptor
.GetConverter(targetType)
.ConvertFrom(value);
}
public static Type StripNullable(Type type)
{
return IsNullable(type)
? type.GenericTypeArguments[0]
: type;
}
}
}

29
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs

@ -109,7 +109,18 @@ namespace Volo.Abp.Application.Services
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L => _localizer ?? (_localizer = StringLocalizerFactory.Create(LocalizationResource));
public IStringLocalizer L
{
get
{
if (_localizer == null)
{
_localizer = CreateLocalizer();
}
return _localizer;
}
}
private IStringLocalizer _localizer;
protected Type LocalizationResource
@ -147,5 +158,21 @@ namespace Volo.Abp.Application.Services
await AuthorizationService.CheckAsync(policyName);
}
protected virtual IStringLocalizer CreateLocalizer()
{
if (LocalizationResource != null)
{
return StringLocalizerFactory.Create(LocalizationResource);
}
var localizer = StringLocalizerFactory.CreateDefaultOrNull();
if (localizer == null)
{
throw new AbpException($"Set {nameof(LocalizationResource)} or define the default localization resource type (by configuring the {nameof(AbpLocalizationOptions)}.{nameof(AbpLocalizationOptions.DefaultResourceType)}) to be able to use the {nameof(L)} object!");
}
return localizer;
}
}
}

1
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -194,6 +194,7 @@ namespace Volo.Abp.EntityFrameworkCore
{
continue;
}
/* Checking "currentValue != null" has a good advantage:
* Assume that you we already using a named extra property,
* then decided to create a field (entity extension) for it.

47
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newtonsoft.Json;
using Volo.Abp.ObjectExtending;
@ -11,7 +12,7 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters
public ExtraPropertiesValueConverter(Type entityType)
: base(
d => SerializeObject(d, entityType),
s => DeserializeObject(s))
s => DeserializeObject(s, entityType))
{
}
@ -38,9 +39,49 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters
return JsonConvert.SerializeObject(copyDictionary, Formatting.None);
}
private static Dictionary<string, object> DeserializeObject(string extraPropertiesAsJson)
private static Dictionary<string, object> DeserializeObject(string extraPropertiesAsJson, Type entityType)
{
return JsonConvert.DeserializeObject<Dictionary<string, object>>(extraPropertiesAsJson);
var dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(extraPropertiesAsJson);
if (entityType != null)
{
var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType);
if (objectExtension != null)
{
foreach (var property in objectExtension.GetProperties())
{
dictionary[property.Name] = GetNormalizedValue(dictionary, property);
}
}
}
return dictionary;
}
private static object GetNormalizedValue(
Dictionary<string, object> dictionary,
ObjectExtensionPropertyInfo property)
{
var value = dictionary.GetOrDefault(property.Name);
if (value == null)
{
return property.GetDefaultValue();
}
try
{
if (property.Type.IsEnum)
{
return Enum.Parse(property.Type, value.ToString(), true);
}
//return Convert.ChangeType(value, property.Type);
return value;
}
catch
{
return value;
}
}
}
}

0
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableString.cs → framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs

0
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceNameAttribute.cs → framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizationResourceNameAttribute.cs

2
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs

@ -177,9 +177,7 @@ namespace Volo.Abp.Localization
return allStrings.Values.ToImmutableList();
}
public class CultureWrapperStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance
{
private readonly string _cultureName;

50
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StringLocalizerHelper.cs

@ -0,0 +1,50 @@
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization
{
/// <summary>
/// This class is designed to be used internal by the framework.
/// </summary>
public static class AbpInternalLocalizationHelper
{
/// <summary>
/// Searches an array of keys in an array of localizers.
/// </summary>
/// <param name="localizers">
/// An array of localizers. Search the keys on the localizers.
/// Can contain null items in the array.
/// </param>
/// <param name="keys">
/// An array of keys. Search the keys on the localizers.
/// Should not contain null items in the array.
/// </param>
/// <param name="defaultValue">
/// Return value if none of the localizers has none of the keys.
/// </param>
/// <returns></returns>
public static string LocalizeWithFallback(
IStringLocalizer[] localizers,
string[] keys,
string defaultValue)
{
foreach (var key in keys)
{
foreach (var localizer in localizers)
{
if (localizer == null)
{
continue;
}
var localizedString = localizer[key];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
}
}
return defaultValue;
}
}
}

9
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs

@ -65,8 +65,13 @@ namespace Volo.Abp.Data
public static TSource SetDefaultsForExtraProperties<TSource>(this TSource source, Type objectType = null)
where TSource : IHasExtraProperties
{
if (objectType == null)
{
objectType = typeof(TSource);
}
var properties = ObjectExtensionManager.Instance
.GetProperties(objectType ?? typeof(TSource));
.GetProperties(objectType);
foreach (var property in properties)
{
@ -75,7 +80,7 @@ namespace Volo.Abp.Data
continue;
}
source.ExtraProperties[property.Name] = TypeHelper.GetDefaultValue(property.Type);
source.ExtraProperties[property.Name] = property.GetDefaultValue();
}
return source;

37
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensionPropertyHelper.cs

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending
{
internal static class ExtensionPropertyHelper
{
public static IEnumerable<Attribute> GetDefaultAttributes(Type type)
{
if (TypeHelper.IsNonNullablePrimitiveType(type) || type.IsEnum)
{
yield return new RequiredAttribute();
}
if (type.IsEnum)
{
yield return new EnumDataTypeAttribute(type);
}
}
public static object GetDefaultValue(
Type propertyType,
Func<object> defaultValueFactory,
object defaultValue)
{
if (defaultValueFactory != null)
{
return defaultValueFactory();
}
return defaultValue ??
TypeHelper.GetDefaultValue(propertyType);
}
}
}

38
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/IBasicObjectExtensionPropertyInfo.cs

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Localization;
namespace Volo.Abp.ObjectExtending
{
public interface IBasicObjectExtensionPropertyInfo
{
[NotNull]
public string Name { get; }
[NotNull]
public Type Type { get; }
[NotNull]
public List<Attribute> Attributes { get; }
[NotNull]
public List<Action<ObjectExtensionPropertyValidationContext>> Validators { get; }
[CanBeNull]
public ILocalizableString DisplayName { get; }
/// <summary>
/// Uses as the default value if <see cref="DefaultValueFactory"/> was not set.
/// </summary>
[CanBeNull]
public object DefaultValue { get; set; }
/// <summary>
/// Used with the first priority to create the default value for the property.
/// Uses to the <see cref="DefaultValue"/> if this was not set.
/// </summary>
[CanBeNull]
public Func<object> DefaultValueFactory { get; set; }
}
}

20
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs

@ -50,6 +50,8 @@ namespace Volo.Abp.ObjectExtending.Modularity
configureAction?.Invoke(propertyInfo);
NormalizeProperty(propertyInfo);
return this;
}
@ -58,5 +60,23 @@ namespace Volo.Abp.ObjectExtending.Modularity
{
return Properties.Values.ToImmutableList();
}
private static void NormalizeProperty(ExtensionPropertyConfiguration propertyInfo)
{
if (!propertyInfo.Api.OnGet.IsAvailable)
{
propertyInfo.UI.OnTable.IsVisible = false;
}
if (!propertyInfo.Api.OnCreate.IsAvailable)
{
propertyInfo.UI.OnCreateForm.IsVisible = false;
}
if (!propertyInfo.Api.OnUpdate.IsAvailable)
{
propertyInfo.UI.OnEditForm.IsVisible = false;
}
}
}
}

24
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfiguration.cs

@ -2,10 +2,11 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Localization;
using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending.Modularity
{
public class ExtensionPropertyConfiguration : IHasNameWithLocalizableDisplayName
public class ExtensionPropertyConfiguration : IHasNameWithLocalizableDisplayName, IBasicObjectExtensionPropertyInfo
{
[NotNull]
public EntityExtensionConfiguration EntityExtensionConfiguration { get; }
@ -45,6 +46,19 @@ namespace Volo.Abp.ObjectExtending.Modularity
[NotNull]
public ExtensionPropertyApiConfiguration Api { get; }
/// <summary>
/// Uses as the default value if <see cref="DefaultValueFactory"/> was not set.
/// </summary>
[CanBeNull]
public object DefaultValue { get; set; }
/// <summary>
/// Used with the first priority to create the default value for the property.
/// Uses to the <see cref="DefaultValue"/> if this was not set.
/// </summary>
[CanBeNull]
public Func<object> DefaultValueFactory { get; set; }
public ExtensionPropertyConfiguration(
[NotNull] EntityExtensionConfiguration entityExtensionConfiguration,
[NotNull] Type type,
@ -61,6 +75,14 @@ namespace Volo.Abp.ObjectExtending.Modularity
Entity = new ExtensionPropertyEntityConfiguration();
UI = new ExtensionPropertyUiConfiguration();
Api = new ExtensionPropertyApiConfiguration();
Attributes.AddRange(ExtensionPropertyHelper.GetDefaultAttributes(Type));
DefaultValue = TypeHelper.GetDefaultValue(Type);
}
public object GetDefaultValue()
{
return ExtensionPropertyHelper.GetDefaultValue(Type, DefaultValueFactory, DefaultValue);
}
}
}

32
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs

@ -0,0 +1,32 @@
using System;
using Volo.Abp.Localization;
namespace Volo.Abp.ObjectExtending.Modularity
{
public static class ExtensionPropertyConfigurationExtensions
{
public static string GetLocalizationResourceNameOrNull(
this ExtensionPropertyConfiguration property)
{
var resourceType = property.GetLocalizationResourceTypeOrNull();
if (resourceType == null)
{
return null;
}
return LocalizationResourceNameAttribute.GetName(resourceType);
}
public static Type GetLocalizationResourceTypeOrNull(
this ExtensionPropertyConfiguration property)
{
if (property.DisplayName != null &&
property.DisplayName is LocalizableString localizableString)
{
return localizableString.ResourceType;
}
return null;
}
}
}

4
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace Volo.Abp.ObjectExtending.Modularity
@ -148,9 +149,12 @@ namespace Volo.Abp.ObjectExtending.Modularity
propertyConfig.Name,
property =>
{
property.Attributes.Clear();
property.Attributes.AddRange(propertyConfig.Attributes);
property.DisplayName = propertyConfig.DisplayName;
property.Validators.AddRange(propertyConfig.Validators);
property.DefaultValue = propertyConfig.DefaultValue;
property.DefaultValueFactory = propertyConfig.DefaultValueFactory;
}
);
}

25
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs

@ -2,12 +2,12 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Microsoft.Extensions.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName
public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName, IBasicObjectExtensionPropertyInfo
{
[NotNull]
public ObjectExtensionInfo ObjectExtension { get; }
@ -48,6 +48,19 @@ namespace Volo.Abp.ObjectExtending
[NotNull]
public Dictionary<object, object> Configuration { get; }
/// <summary>
/// Uses as the default value if <see cref="DefaultValueFactory"/> was not set.
/// </summary>
[CanBeNull]
public object DefaultValue { get; set; }
/// <summary>
/// Used with the first priority to create the default value for the property.
/// Uses to the <see cref="DefaultValue"/> if this was not set.
/// </summary>
[CanBeNull]
public Func<object> DefaultValueFactory { get; set; }
public ObjectExtensionPropertyInfo(
[NotNull] ObjectExtensionInfo objectExtension,
[NotNull] Type type,
@ -61,6 +74,14 @@ namespace Volo.Abp.ObjectExtending
ValidationAttributes = new List<ValidationAttribute>();
Attributes = new List<Attribute>();
Validators = new List<Action<ObjectExtensionPropertyValidationContext>>();
Attributes.AddRange(ExtensionPropertyHelper.GetDefaultAttributes(Type));
DefaultValue = TypeHelper.GetDefaultValue(Type);
}
public object GetDefaultValue()
{
return ExtensionPropertyHelper.GetDefaultValue(Type, DefaultValueFactory, DefaultValue);
}
}
}

22
framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/TypeHelper_Tests.cs

@ -7,6 +7,13 @@ namespace Volo.Abp.Reflection
{
public class TypeHelper_Tests
{
[Fact]
public void IsNonNullablePrimitiveType()
{
TypeHelper.IsNonNullablePrimitiveType(typeof(int)).ShouldBeTrue();
TypeHelper.IsNonNullablePrimitiveType(typeof(string)).ShouldBeFalse();
}
[Fact]
public void Should_Generic_Type_From_Nullable()
{
@ -73,11 +80,26 @@ namespace Volo.Abp.Reflection
TypeHelper.GetDefaultValue(typeof(byte)).ShouldBe(0);
TypeHelper.GetDefaultValue(typeof(int)).ShouldBe(0);
TypeHelper.GetDefaultValue(typeof(string)).ShouldBeNull();
TypeHelper.GetDefaultValue(typeof(MyEnum)).ShouldBe(MyEnum.EnumValue0);
}
[Fact]
public void ConvertFromString()
{
TypeHelper.ConvertFromString<int>("42").ShouldBe(42);
TypeHelper.ConvertFromString<int?>("42").ShouldBe((int?)42);
TypeHelper.ConvertFromString<int?>(null).ShouldBeNull();
}
public class MyDictionary : Dictionary<bool, TypeHelper_Tests>
{
}
public enum MyEnum
{
EnumValue0,
EnumValue1,
}
}
}

6
framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json

@ -0,0 +1,6 @@
{
"culture": "de",
"texts": {
"hello": "Hello"
}
}

132
framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs

@ -1,4 +1,5 @@
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Shouldly;
using Xunit;
@ -22,7 +23,7 @@ namespace Volo.Abp.ObjectExtending
var objectExtension = _objectExtensionManager.GetOrNull<MyExtensibleObject>();
objectExtension.ShouldNotBeNull();
var properties = objectExtension.GetProperties();
properties.Count.ShouldBe(1);
properties.FirstOrDefault(p => p.Name == "TestProp").ShouldNotBeNull();
@ -55,9 +56,136 @@ namespace Volo.Abp.ObjectExtending
property.Configuration["TestConfig2"].ShouldBe("TestConfig2-Value");
}
[Fact]
public void Should_Automatically_Add_RequiredAttribute_To_Non_Nullable_Types_And_Enums()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, int>("IntProp")
.AddOrUpdateProperty<MyExtensibleObject, bool>("BoolProp")
.AddOrUpdateProperty<MyExtensibleObject, int?>("NullableIntProp")
.AddOrUpdateProperty<MyExtensibleObject, string>("StringProp")
.AddOrUpdateProperty<MyExtensibleObject, MyTestEnum>("EnumProp");
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntProp")
.Attributes
.ShouldContain(x => x is RequiredAttribute);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("BoolProp")
.Attributes
.ShouldContain(x => x is RequiredAttribute);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("EnumProp")
.Attributes
.ShouldContain(x => x is RequiredAttribute);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntProp")
.Attributes
.ShouldNotContain(x => x is RequiredAttribute);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringProp")
.Attributes
.ShouldNotContain(x => x is RequiredAttribute);
}
[Fact]
public void Should_Automatically_Add_EnumDataTypeAttribute_For_Enums()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, MyTestEnum>("EnumProp");
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("EnumProp")
.Attributes
.ShouldContain(x => x is EnumDataTypeAttribute);
}
[Fact]
public void Should_Be_Able_To_Clear_Auto_Added_Attributes()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, int>("IntProp", property =>
{
property.Attributes.Clear();
});
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntProp")
.Attributes
.ShouldNotContain(x => x is RequiredAttribute);
}
[Fact]
public void Should_Set_DefaultValues()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, int>("IntProp")
.AddOrUpdateProperty<MyExtensibleObject, int>("IntPropWithCustomDefaultValue", property =>
{
property.DefaultValue = 42;
})
.AddOrUpdateProperty<MyExtensibleObject, bool>("BoolProp")
.AddOrUpdateProperty<MyExtensibleObject, int?>("NullableIntProp")
.AddOrUpdateProperty<MyExtensibleObject, int?>("NullableIntPropWithCustomDefaultValueFactory", property =>
{
property.DefaultValueFactory = () => 2;
})
.AddOrUpdateProperty<MyExtensibleObject, string>("StringProp")
.AddOrUpdateProperty<MyExtensibleObject, string>("StringPropWithCustomDefaultValue", property =>
{
property.DefaultValue = "custom-value";
})
.AddOrUpdateProperty<MyExtensibleObject, MyTestEnum>("EnumProp", property =>
{
property.DefaultValue = MyTestEnum.EnumValue2;
});
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntProp")
.GetDefaultValue().ShouldBe(0);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntPropWithCustomDefaultValue")
.GetDefaultValue().ShouldBe(42);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("BoolProp")
.GetDefaultValue().ShouldBe(false);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntProp")
.GetDefaultValue().ShouldBeNull();
var propWithDefaultValueFactory = _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntPropWithCustomDefaultValueFactory");
propWithDefaultValueFactory.GetDefaultValue().ShouldBe(2);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringProp")
.GetDefaultValue().ShouldBeNull();
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringPropWithCustomDefaultValue")
.GetDefaultValue().ShouldBe("custom-value");
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("EnumProp")
.GetDefaultValue().ShouldBe(MyTestEnum.EnumValue2);
}
private class MyExtensibleObject : ExtensibleObject
{
}
private enum MyTestEnum
{
EnumValue1,
EnumValue2,
}
}
}

12
modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs

@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Volo.Blogging
{
public class BloggingTwitterOptions
{
public string Site { get; set; }
}
}

6
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml

@ -8,6 +8,7 @@
@using Volo.Blogging.Pages.Blog.Posts
@using Volo.Blogging.Areas.Blog.Helpers.TagHelpers
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs
@using Volo.Blogging.SocialMedia
@inject IAuthorizationService Authorization
@inject IOptionsSnapshot<BloggingTwitterOptions> twitterOptions
@model DetailModel
@ -21,6 +22,11 @@
ViewBag.TwitterDescription = Model.Post.Description;
ViewBag.TwitterImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}";
ViewBag.LinkedInUrl = Request.GetEncodedUrl();
ViewBag.LinkedInTitle = Model.Post.Title;
ViewBag.LinkedInDescription = Model.Post.Description;
ViewBag.LinkedInImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}";
var hasCommentingPermission = CurrentUser.IsAuthenticated; //TODO: Apply real policy!
}
@section scripts {

7
modules/blogging/src/Volo.Blogging.Web/SocialMedia/BloggingTwitterOptions.cs

@ -0,0 +1,7 @@
namespace Volo.Blogging.SocialMedia
{
public class BloggingTwitterOptions
{
public string Site { get; set; }
}
}

23
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs

@ -15,13 +15,13 @@ namespace Volo.Abp.Identity.MongoDB
{
public class MongoIdentityUserRepository : MongoDbRepository<IAbpIdentityMongoDbContext, IdentityUser, Guid>, IIdentityUserRepository
{
public MongoIdentityUserRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider)
public MongoIdentityUserRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public virtual async Task<IdentityUser> FindByNormalizedUserNameAsync(
string normalizedUserName,
string normalizedUserName,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
@ -33,7 +33,7 @@ namespace Volo.Abp.Identity.MongoDB
}
public virtual async Task<List<string>> GetRoleNamesAsync(
Guid id,
Guid id,
CancellationToken cancellationToken = default)
{
var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken));
@ -42,8 +42,8 @@ namespace Volo.Abp.Identity.MongoDB
}
public virtual async Task<IdentityUser> FindByLoginAsync(
string loginProvider,
string providerKey,
string loginProvider,
string providerKey,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
@ -71,7 +71,7 @@ namespace Volo.Abp.Identity.MongoDB
}
public virtual async Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
string normalizedRoleName,
string normalizedRoleName,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
@ -89,9 +89,9 @@ namespace Volo.Abp.Identity.MongoDB
public virtual async Task<List<IdentityUser>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
@ -100,7 +100,8 @@ namespace Volo.Abp.Identity.MongoDB
!filter.IsNullOrWhiteSpace(),
u =>
u.UserName.Contains(filter) ||
u.Email.Contains(filter)
u.Email.Contains(filter) ||
(u.PhoneNumber != null && u.PhoneNumber.Contains(filter))
)
.OrderBy(sorting ?? nameof(IdentityUser.UserName))
.As<IMongoQueryable<IdentityUser>>()
@ -132,4 +133,4 @@ namespace Volo.Abp.Identity.MongoDB
.LongCountAsync(GetCancellationToken(cancellationToken));
}
}
}
}

28
npm/ng-packs/package.json

@ -22,20 +22,20 @@
"generate:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},
"devDependencies": {
"@abp/ng.account": "~2.6.2",
"@abp/ng.account.config": "~2.6.2",
"@abp/ng.core": "^2.6.2",
"@abp/ng.feature-management": "^2.6.2",
"@abp/ng.identity": "~2.6.2",
"@abp/ng.identity.config": "~2.6.2",
"@abp/ng.permission-management": "^2.6.2",
"@abp/ng.setting-management": "~2.6.2",
"@abp/ng.setting-management.config": "~2.6.2",
"@abp/ng.tenant-management": "~2.6.2",
"@abp/ng.tenant-management.config": "~2.6.2",
"@abp/ng.theme.basic": "~2.6.2",
"@abp/ng.theme.shared": "^2.6.2",
"@abp/utils": "^2.6.0",
"@abp/ng.account": "~2.7.0",
"@abp/ng.account.config": "~2.7.0",
"@abp/ng.core": "^2.7.0",
"@abp/ng.feature-management": "^2.7.0",
"@abp/ng.identity": "~2.7.0",
"@abp/ng.identity.config": "~2.7.0",
"@abp/ng.permission-management": "~2.7.0",
"@abp/ng.setting-management": "~2.7.0",
"@abp/ng.setting-management.config": "~2.7.0",
"@abp/ng.tenant-management": "~2.7.0",
"@abp/ng.tenant-management.config": "~2.7.0",
"@abp/ng.theme.basic": "~2.7.0",
"@abp/ng.theme.shared": "^2.7.0",
"@abp/utils": "^2.7.0",
"@angular-builders/jest": "^8.2.0",
"@angular-devkit/build-angular": "~0.803.21",
"@angular-devkit/build-ng-packagr": "~0.803.21",

2
npm/ng-packs/packages/core/package.json

@ -7,7 +7,7 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/utils": "^2.6.0",
"@abp/utils": "^2.7.0",
"@angular/localize": "~9.1.0",
"@ngxs/router-plugin": "^3.6.2",
"@ngxs/storage-plugin": "^3.6.2",

1
npm/ng-packs/packages/core/src/lib/services/index.ts

@ -4,6 +4,7 @@ export * from './config-state.service';
export * from './content-projection.service';
export * from './dom-insertion.service';
export * from './lazy-load.service';
export * from './list.service';
export * from './localization.service';
export * from './profile-state.service';
export * from './profile.service';

101
npm/ng-packs/packages/core/src/lib/services/list.service.ts

@ -0,0 +1,101 @@
import { Inject, Injectable, OnDestroy, Optional } from '@angular/core';
import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
import { debounceTime, shareReplay, switchMap, tap } from 'rxjs/operators';
import { ABP } from '../models/common';
import { PagedResultDto } from '../models/dtos';
import { LIST_QUERY_DEBOUNCE_TIME } from '../tokens/list.token';
import { takeUntilDestroy } from '../utils/rxjs-utils';
@Injectable()
export class ListService implements OnDestroy {
private _filter = '';
set filter(value: string) {
this._filter = value;
this.get();
}
get filter(): string {
return this._filter;
}
private _maxResultCount = 10;
set maxResultCount(value: number) {
this._maxResultCount = value;
this.get();
}
get maxResultCount(): number {
return this._maxResultCount;
}
private _page = 1;
set page(value: number) {
this._page = value;
this.get();
}
get page(): number {
return this._page;
}
private _sortKey = '';
set sortKey(value: string) {
this._sortKey = value;
this.get();
}
get sortKey(): string {
return this._sortKey;
}
private _sortOrder = '';
set sortOrder(value: string) {
this._sortOrder = value;
this.get();
}
get sortOrder(): string {
return this._sortOrder;
}
private _query$ = new ReplaySubject<ABP.PageQueryParams>(1);
get query$(): Observable<ABP.PageQueryParams> {
return this._query$
.asObservable()
.pipe(debounceTime(this.delay || 300), shareReplay({ bufferSize: 1, refCount: true }));
}
private _isLoading$ = new BehaviorSubject(false);
get isLoading$(): Observable<boolean> {
return this._isLoading$.asObservable();
}
get = () => {
this._query$.next({
filter: this._filter || undefined,
maxResultCount: this._maxResultCount,
skipCount: (this._page - 1) * this._maxResultCount,
sorting: this._sortOrder ? `${this._sortKey} ${this._sortOrder}` : undefined,
});
};
constructor(@Optional() @Inject(LIST_QUERY_DEBOUNCE_TIME) private delay: number) {
this.get();
}
hookToQuery<T extends any>(
streamCreatorCallback: QueryStreamCreatorCallback<T>,
): Observable<PagedResultDto<T>> {
this._isLoading$.next(true);
return this.query$.pipe(
switchMap(streamCreatorCallback),
tap(() => this._isLoading$.next(false)),
shareReplay({ bufferSize: 1, refCount: true }),
takeUntilDestroy(this),
);
}
ngOnDestroy() {}
}
export type QueryStreamCreatorCallback<T> = (
query: ABP.PageQueryParams,
) => Observable<PagedResultDto<T>>;

153
npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts

@ -0,0 +1,153 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { bufferCount, take } from 'rxjs/operators';
import { ABP } from '../models';
import { ListService, QueryStreamCreatorCallback } from '../services/list.service';
import { LIST_QUERY_DEBOUNCE_TIME } from '../tokens';
describe('ListService', () => {
let spectator: SpectatorService<ListService>;
let service: ListService;
const createService = createServiceFactory({
service: ListService,
providers: [
{
provide: LIST_QUERY_DEBOUNCE_TIME,
useValue: 0,
},
],
});
beforeEach(() => {
spectator = createService();
service = spectator.service;
});
describe('#filter', () => {
it('should initially be empty string', () => {
expect(service.filter).toBe('');
});
it('should be changed', () => {
service.filter = 'foo';
expect(service.filter).toBe('foo');
});
});
describe('#maxResultCount', () => {
it('should initially be 10', () => {
expect(service.maxResultCount).toBe(10);
});
it('should be changed', () => {
service.maxResultCount = 20;
expect(service.maxResultCount).toBe(20);
});
});
describe('#page', () => {
it('should initially be 1', () => {
expect(service.page).toBe(1);
});
it('should be changed', () => {
service.page = 9;
expect(service.page).toBe(9);
});
});
describe('#sortKey', () => {
it('should initially be empty string', () => {
expect(service.sortKey).toBe('');
});
it('should be changed', () => {
service.sortKey = 'foo';
expect(service.sortKey).toBe('foo');
});
});
describe('#sortOrder', () => {
it('should initially be empty string', () => {
expect(service.sortOrder).toBe('');
});
it('should be changed', () => {
service.sortOrder = 'foo';
expect(service.sortOrder).toBe('foo');
});
});
describe('#query$', () => {
it('should initially emit default query', done => {
service.query$.pipe(take(1)).subscribe(query => {
expect(query).toEqual({
filter: undefined,
maxResultCount: 10,
skipCount: 0,
sorting: undefined,
});
done();
});
});
it('should emit a query based on params set', done => {
service.filter = 'foo';
service.sortKey = 'bar';
service.sortOrder = 'baz';
service.maxResultCount = 20;
service.page = 9;
service.query$.pipe(take(1)).subscribe(query => {
expect(query).toEqual({
filter: 'foo',
sorting: 'bar baz',
maxResultCount: 20,
skipCount: 160,
});
done();
});
});
});
describe('#hookToQuery', () => {
it('should call given callback with the query', done => {
const callback: QueryStreamCreatorCallback<ABP.PageQueryParams> = query =>
of({ items: [query], totalCount: 1 });
service.hookToQuery(callback).subscribe(({ items: [query] }) => {
expect(query).toEqual({
filter: undefined,
maxResultCount: 10,
skipCount: 0,
sorting: undefined,
});
done();
});
});
it('should emit isLoading as side effect', done => {
const callback: QueryStreamCreatorCallback<ABP.PageQueryParams> = query =>
of({ items: [query], totalCount: 1 });
service.isLoading$.pipe(bufferCount(3)).subscribe(([idle, init, end]) => {
expect(idle).toBe(false);
expect(init).toBe(true);
expect(end).toBe(false);
done();
});
service.hookToQuery(callback).subscribe();
});
});
});

1
npm/ng-packs/packages/core/src/lib/tokens/index.ts

@ -1 +1,2 @@
export * from './list.token';
export * from './options.token';

3
npm/ng-packs/packages/core/src/lib/tokens/list.token.ts

@ -0,0 +1,3 @@
import { InjectionToken } from '@angular/core';
export const LIST_QUERY_DEBOUNCE_TIME = new InjectionToken<number>('LIST_QUERY_DEBOUNCE_TIME');

2
npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts

@ -2,6 +2,7 @@ import {
DynamicLayoutComponent,
ReplaceableComponents,
ReplaceableRouteContainerComponent,
AuthGuard,
} from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
@ -12,6 +13,7 @@ const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
canActivate: [AuthGuard],
children: [
{
path: '',

5
npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.scss

@ -16,6 +16,9 @@
left: 0;
position: fixed;
top: 0;
transition: width 0.4s ease;
&.progressing {
transition: width 0.4s ease;
}
}
}

63
npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts

@ -3,7 +3,7 @@ import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular
import { NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';
import { takeUntilDestroy } from '@ngx-validate/core';
import { Actions, ofActionSuccessful } from '@ngxs/store';
import { interval, Subscription, timer } from 'rxjs';
import { Subscription, timer } from 'rxjs';
import { filter } from 'rxjs/operators';
@Component({
@ -12,6 +12,7 @@ import { filter } from 'rxjs/operators';
<div id="abp-loader-bar" [ngClass]="containerClass" [class.is-loading]="isLoading">
<div
class="abp-progress"
[class.progressing]="progressLevel"
[style.width.vw]="progressLevel"
[ngStyle]="{
'background-color': color,
@ -29,6 +30,10 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
@Input()
color = '#77b6ff';
@Input()
filter = (action: StartLoader | StopLoader) =>
action.payload.url.indexOf('openid-configuration') < 0;
@Input()
isLoading = false;
@ -40,11 +45,25 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
intervalPeriod = 350;
stopDelay = 820;
@Input()
filter = (action: StartLoader | StopLoader) =>
action.payload.url.indexOf('openid-configuration') < 0;
stopDelay = 800;
private readonly clearProgress = () => {
this.progressLevel = 0;
this.cdRef.detectChanges();
};
private readonly reportProgress = () => {
if (this.progressLevel < 75) {
this.progressLevel += 1 + Math.random() * 9;
} else if (this.progressLevel < 90) {
this.progressLevel += 0.4;
} else if (this.progressLevel < 100) {
this.progressLevel += 0.1;
} else {
this.interval.unsubscribe();
}
this.cdRef.detectChanges();
};
get boxShadow(): string {
return `0 0 10px rgba(${this.color}, 0.5)`;
@ -52,7 +71,7 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
constructor(private actions: Actions, private router: Router, private cdRef: ChangeDetectorRef) {}
ngOnInit() {
private subscribeToLoadActions() {
this.actions
.pipe(
ofActionSuccessful(StartLoader, StopLoader),
@ -63,7 +82,9 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
if (action instanceof StartLoader) this.startLoading();
else this.stopLoading();
});
}
private subscribeToRouterEvents() {
this.router.events
.pipe(
filter(
@ -80,37 +101,31 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
});
}
ngOnInit() {
this.subscribeToLoadActions();
this.subscribeToRouterEvents();
}
ngOnDestroy() {
if (this.interval) this.interval.unsubscribe();
}
startLoading() {
if (this.isLoading || this.progressLevel !== 0) return;
if (this.isLoading || (this.interval && !this.interval.closed)) return;
this.isLoading = true;
this.interval = interval(this.intervalPeriod).subscribe(() => {
if (this.progressLevel < 75) {
this.progressLevel += Math.random() * 10;
} else if (this.progressLevel < 90) {
this.progressLevel += 0.4;
} else if (this.progressLevel < 100) {
this.progressLevel += 0.1;
} else {
this.interval.unsubscribe();
}
this.cdRef.detectChanges();
});
this.interval = timer(0, this.intervalPeriod).subscribe(this.reportProgress);
}
stopLoading() {
if (this.interval) this.interval.unsubscribe();
this.progressLevel = 100;
this.isLoading = false;
if (this.timer && !this.timer.closed) return;
this.timer = timer(this.stopDelay).subscribe(() => {
this.progressLevel = 0;
this.cdRef.detectChanges();
});
this.timer = timer(this.stopDelay).subscribe(this.clearProgress);
}
}

141
npm/ng-packs/yarn.lock

@ -2,26 +2,26 @@
# yarn lockfile v1
"@abp/ng.account.config@^2.6.2", "@abp/ng.account.config@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.6.2.tgz#1a3e935b7144525c62f5d08adcd06d2ee12f7c9e"
integrity sha512-dC0/H1dkeQqYbu/dNISQA7NLpdOughhZSxT/7zjq6cBP8gYDceW0wYT5Rh0qIAkYWh2jO4BMXIFqzlH31Jg/XA==
"@abp/ng.account.config@^2.7.0", "@abp/ng.account.config@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.7.0.tgz#ebd8ccb988668b29017f6e2338f5f72993c93af9"
integrity sha512-46bCayLnErut8naJORSbht0bfAKRRwsvBc4n6mj1oMKasjxVJC6Qc7P0QgnmuvSC3UZRexc2K9CeC+zLg1E7xQ==
dependencies:
tslib "^1.9.0"
"@abp/ng.account@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.6.2.tgz#aac921c49bcd8cdec506bd27d2546193416ea187"
integrity sha512-s3gLSqNdCW6D+DvuML+aGFT8braCfSXUbxvJ1WRCovd2Ec0jNxPBKj84zgYPYEb3smD294DcXxNx36jtJK+1OA==
"@abp/ng.account@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.7.0.tgz#f211fe8b7aa85c2744ab194189f9b00c601b5b3f"
integrity sha512-C8hlhE2K0dPyHoQlgahPbnK/DefVFTDsdIpzbODywHfKZQN5RcRhESejVE2B0dEzLEXmvDwIMg0rQFt4GV/QEg==
dependencies:
"@abp/ng.account.config" "^2.6.2"
"@abp/ng.theme.shared" "^2.6.2"
"@abp/ng.account.config" "^2.7.0"
"@abp/ng.theme.shared" "^2.7.0"
tslib "^1.9.0"
"@abp/ng.core@^2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.6.2.tgz#d0e4f6666a9a207fc7425001449bea2cdfe93522"
integrity sha512-e7c82d9VF20d4JFUkyzT5iTqGKnkq5FBHdv6IW6lsVyuz2Zf/p4wNtKATRr2ntM7qNIjIQebtGNFyxxX4nmL+w==
"@abp/ng.core@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.7.0.tgz#1a681deeeca02bcf3bd2722429d1d8d5e5cacc4f"
integrity sha512-ODy5r1kYcQngR2tHtvauYS5BxT9MhNfOFnEg/1qDuGh90aca6WVRlTTfeeU4/DRbvRVRLnwr/k6fQiwOr+NyAQ==
dependencies:
"@abp/utils" "^2.6.0"
"@angular/localize" "~9.1.0"
@ -35,86 +35,86 @@
ts-toolbelt "^6.3.6"
tslib "^1.9.0"
"@abp/ng.feature-management@^2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.6.2.tgz#da09b07ebe81f09ccf338a7c10c384920375567a"
integrity sha512-JdHuAZk9j9AR1iKU02Xoux40ULOccm+68q7/4L2G1cBET6yTAE8Ua9RPPX9HadmCG0cR8awl3d+5JPwyL3OjFA==
"@abp/ng.feature-management@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.7.0.tgz#a0ebc00ea3fd13e5727bef8c2de503cdbda10c7c"
integrity sha512-NyoZaBvfeAYvt8PJGekuwC8c0Tv9Yt4nrHT9fOt+P7pWYjoT+q3Q9OZWd6IBpi7o6UVnzk4r1CYZ5rX7kUT3iQ==
dependencies:
"@abp/ng.theme.shared" "^2.6.2"
"@abp/ng.theme.shared" "^2.7.0"
tslib "^1.9.0"
"@abp/ng.identity.config@^2.6.2", "@abp/ng.identity.config@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.6.2.tgz#79384ae4b11e53e8bf9dda25d3400def05c5beb8"
integrity sha512-jCqt2ZWEkRA9foJjHwRJtw5JNy03OVge9dUj0eAJ7Vo13IrKAT1kCNlD7cRM9xJ42Ga2utEqqz9FCJHgR2IxbQ==
"@abp/ng.identity.config@^2.7.0", "@abp/ng.identity.config@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.7.0.tgz#8ec7c86d586a6ea632bd984f87063194a6098d07"
integrity sha512-MV3erK39rLMDs+Y29p103k5Pci6MVGcVlgdg29QwBVDzvTKY8NpxP9KTGjqwCHyha8B3lVo39ZqDgJNqOvYhzg==
dependencies:
tslib "^1.9.0"
"@abp/ng.identity@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.6.2.tgz#14f83305415d27b67d01405e582978682cfd7f22"
integrity sha512-wzR/QFoSSRhIxfH91QsTSCc0rBsFfsg8aiP5xPheNx3/lZygCnMogH3blRgLiNIoyxuG00MTfp+e9lFIBfDnbQ==
"@abp/ng.identity@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.7.0.tgz#f036d523bec04078fe718af3c460eab3893297ab"
integrity sha512-QDrNiUyrLfOA7iDTG7oMcCLpe9CmkhGMKA0fTcxHkwSl39xeED+bd9iGfnVIDiqRFxnp3jQHBhSwaM/Gkh8gnQ==
dependencies:
"@abp/ng.identity.config" "^2.6.2"
"@abp/ng.permission-management" "^2.6.2"
"@abp/ng.theme.shared" "^2.6.2"
"@abp/ng.identity.config" "^2.7.0"
"@abp/ng.permission-management" "^2.7.0"
"@abp/ng.theme.shared" "^2.7.0"
tslib "^1.9.0"
"@abp/ng.permission-management@^2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.6.2.tgz#ba7902256acf3e1ff49552983e4d292f670f9ad8"
integrity sha512-6NW5BF+5ZFQI8oLEQWCiJS5iF2v3gHLvohrbN84RsPNTc+ZRDte6MbIOGq2CWxM8qXwisLBiR/RWC4ssgfPqEA==
"@abp/ng.permission-management@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.7.0.tgz#a71f8f31c7ff2875ebaa91acddd035df0b703369"
integrity sha512-kQ+LOXS6zzeYJzt7mySNK37kP8ZBBvXIljv93OWoHMjd8ZDMhnpinhlojlVK4AAX85ypnTGFs4ZqCUK5ooFkSQ==
dependencies:
"@abp/ng.theme.shared" "^2.6.2"
"@abp/ng.theme.shared" "^2.7.0"
tslib "^1.9.0"
"@abp/ng.setting-management.config@^2.6.2", "@abp/ng.setting-management.config@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.6.2.tgz#43e9e4399d65efc1aee6e314469c303ce22145e9"
integrity sha512-3F0vq5nvBTat2q7S0iBfdEsyPuZHJuL3+9z4Voq4Q9rA2T2VIAUMp9aBsfI2s2ycwGbsb7kCOWTKo/2by1b4AA==
"@abp/ng.setting-management.config@^2.7.0", "@abp/ng.setting-management.config@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.7.0.tgz#98b68ddab5797920bc52bdfb3a3c5cb7ecdbeaa5"
integrity sha512-KPDLe36ahJpoVrVEOukhJXMpAPe44KoS4F92eamhtuD51rPwHgzRyAvn/JKT88n/7vtZfPGwt9LhjSBWgJ8Mag==
dependencies:
tslib "^1.9.0"
"@abp/ng.setting-management@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.6.2.tgz#f49d361ed8412380fdf2173f28b890115748a52b"
integrity sha512-0VdGa+t8lEZRsUdKL1EHoZei+ZCDMTu4n3UvXvD0OzRJyYPUjHRrMSSOYifuN4gPatluIe0E2I+tO3lQ3/2tjw==
"@abp/ng.setting-management@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.7.0.tgz#8f0691b5f1ade31fa414891edb675cbaffc30503"
integrity sha512-4WUHMtN6wYmqwBAEwAfazrMnUnco9n8G9ryqZbdcfiiQvK6GpraCvWU3VjuWDm4KaUb9B1cR8kORRmJXeBz+3g==
dependencies:
"@abp/ng.setting-management.config" "^2.6.2"
"@abp/ng.theme.shared" "^2.6.2"
"@abp/ng.setting-management.config" "^2.7.0"
"@abp/ng.theme.shared" "^2.7.0"
tslib "^1.9.0"
"@abp/ng.tenant-management.config@^2.6.2", "@abp/ng.tenant-management.config@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.6.2.tgz#9a9365ad59bdd2053d7bd33fb623ce633de40b24"
integrity sha512-iZ49Els+J9lbRKTSeNU/KIJuJS7Le3dAk1pdqLg/g5iw7e6uTEih1qeDvJdcXpSKfAZJrOd/KKDjFHIzVrveuA==
"@abp/ng.tenant-management.config@^2.7.0", "@abp/ng.tenant-management.config@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.7.0.tgz#6c363edb2c8052c5231ac852e94e0c51a35716f8"
integrity sha512-QTdd3ibreMHPXIvTJIVODGGmtBwv11dsJw4KNisOAo+D8KcmzK81lgBuYAJEaULnbIMaeEtrI5uNlFqGOKisoQ==
dependencies:
tslib "^1.9.0"
"@abp/ng.tenant-management@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.6.2.tgz#6be284fe6691946e2e6766e34ae958d7d2ec5e3a"
integrity sha512-BGARok01Eqn1kEltOwjwIpMJ8LD0jtYfbSbnO8V9TRg7f4ZEqFmfXudOIluDIwHjSEkNXk+Cs0/iV53lyi5+lg==
"@abp/ng.tenant-management@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.7.0.tgz#8c2e1dc7c7249a44edd5c26de9ad90b8b1d50443"
integrity sha512-Ksi5zCf1pew2kVuF168BvUcbRZXg4avpQs31xnm8gN+7gA2T9hMVOnhqr0fUP+mymjPtQk3w11Db3ZmH3h9A8A==
dependencies:
"@abp/ng.feature-management" "^2.6.2"
"@abp/ng.tenant-management.config" "^2.6.2"
"@abp/ng.theme.shared" "^2.6.2"
"@abp/ng.feature-management" "^2.7.0"
"@abp/ng.tenant-management.config" "^2.7.0"
"@abp/ng.theme.shared" "^2.7.0"
tslib "^1.9.0"
"@abp/ng.theme.basic@~2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.6.2.tgz#4b0414411e164885b8c467b04d635a0a3c81bd01"
integrity sha512-qgA4hh7ipIWY88JOqjChjoJgFB0csqXbwdb/pabrsFoHYuiFVT3cqvHq6jMfKQHEPBJSEnl6/GUIm+26DQ5eDw==
"@abp/ng.theme.basic@~2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.7.0.tgz#8b01f0742582654f5e326357e7e7322e9e237a5c"
integrity sha512-+FTQRAtJW3ZqI1DVgNeZrrIGARVElSZKCmTcx3Vj2cOWg9TzAY2kQAx5U6ByRLYFfHxN9gVRJTcuTUIN3XqYNQ==
dependencies:
"@abp/ng.theme.shared" "^2.6.2"
"@abp/ng.theme.shared" "^2.7.0"
tslib "^1.9.0"
"@abp/ng.theme.shared@^2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.6.2.tgz#9d70b7aab010af4cd5a451c9c2d162d73ec5f9cc"
integrity sha512-e3QVsPpmbPGmTpQY9J1RgmWer7Tx7mrsOtxjFvbJGqKVF1UUtU5Kc3VyGPb3JsjQtT+890/9qmAf/YSj3cNPIA==
"@abp/ng.theme.shared@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.7.0.tgz#c491497033975e294a83e29ad8d745b52f638967"
integrity sha512-tk4BUm7B86l3liq1noFvDvAlJn98YxUFceQaItLhz73BwzIvlUXieIQXEni6CjXAyEiPHcnCJ4l4EssyFXmmNA==
dependencies:
"@abp/ng.core" "^2.6.2"
"@abp/ng.core" "^2.7.0"
"@fortawesome/fontawesome-free" "^5.12.1"
"@ng-bootstrap/ng-bootstrap" "^5.3.0"
"@ngx-validate/core" "^0.0.7"
@ -2129,14 +2129,7 @@
tree-kill "1.2.2"
webpack-sources "1.4.3"
"@ngx-validate/core@^0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@ngx-validate/core/-/core-0.0.7.tgz#35a4364e8c8bb43ce1731e8d681ce1ab307c5955"
integrity sha512-APT7kstDaJ0JkC6cDkypbVSXAOAEyPTQ9P8DjEI1szi+Xzuz69XLASK3NdTYjFwogWevhYXoYRaR2Bq9iWzl+g==
dependencies:
tslib "^1.9.0"
"@ngx-validate/core@^0.0.8":
"@ngx-validate/core@^0.0.7", "@ngx-validate/core@^0.0.8":
version "0.0.8"
resolved "https://registry.yarnpkg.com/@ngx-validate/core/-/core-0.0.8.tgz#8577405eb1af0f5002cdda7a86fbcda56280f116"
integrity sha512-caIG5ao76Xhf7T+pNA8crnpAwK0yqj3i0OAUGZRUq1W+kNgz+ZnrSd4F9an/W4g+38u/8gfUVvsfsm07ju6qYA==

5
npm/packs/jstree/abp.resourcemapping.js

@ -0,0 +1,5 @@
module.exports = {
mappings: {
"@node_modules/jstree/dist/**/*.*": "@libs/jstree/"
}
}

12
npm/packs/jstree/package.json

@ -0,0 +1,12 @@
{
"version": "2.7.0",
"name": "@abp/jstree",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@abp/jquery": "^2.7.0",
"jstree": "^3.3.9"
},
"gitHead": "0ea3895f3b0b489e3ea81fc88f8f0896b22b61bd"
}

2
samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs

@ -95,12 +95,12 @@ namespace AuthServer.Host
app.UseCorrelationId();
app.UseVirtualFiles();
app.UseRouting();
app.UseAbpRequestLocalization();
if (MsDemoConsts.IsMultiTenancyEnabled)
{
app.UseMultiTenancy();
}
app.UseIdentityServer();
app.UseAbpRequestLocalization();
app.UseAuditing();
app.UseConfiguredEndpoints();

6
samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminAppHostModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
@ -119,12 +119,14 @@ namespace BackendAdminApp.Host
app.UseVirtualFiles();
app.UseRouting();
app.UseAuthentication();
if (MsDemoConsts.IsMultiTenancyEnabled)
{
app.UseMultiTenancy();
}
app.UseAuthorization();
app.UseAbpRequestLocalization();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(options =>
{

6
samples/MicroserviceDemo/applications/PublicWebSite.Host/PublicWebSiteHostModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
@ -97,12 +97,14 @@ namespace PublicWebSite.Host
app.UseVirtualFiles();
app.UseRouting();
app.UseAuthentication();
if (MsDemoConsts.IsMultiTenancyEnabled)
{
app.UseMultiTenancy();
}
app.UseAuthorization();
app.UseAbpRequestLocalization();
app.UseAuthorization();
app.UseConfiguredEndpoints();
}
}

4
samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingServiceHostModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -136,10 +136,12 @@ namespace BloggingService.Host
currentPrincipalAccessor.Principal.AddIdentity(new ClaimsIdentity(mapClaims.Select(p => new Claim(map[p.Type], p.Value, p.ValueType, p.Issuer))));
await next();
});
if (MsDemoConsts.IsMultiTenancyEnabled)
{
app.UseMultiTenancy();
}
app.UseAbpRequestLocalization(); //TODO: localization?
app.UseSwagger();
app.UseSwaggerUI(options =>

5
samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityServiceHostModule.cs

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Builder;
@ -102,10 +102,12 @@ namespace IdentityService.Host
app.UseVirtualFiles();
app.UseRouting();
app.UseAuthentication();
if (MsDemoConsts.IsMultiTenancyEnabled)
{
app.UseMultiTenancy();
}
app.Use(async (ctx, next) =>
{
var currentPrincipalAccessor = ctx.RequestServices.GetRequiredService<ICurrentPrincipalAccessor>();
@ -120,7 +122,6 @@ namespace IdentityService.Host
currentPrincipalAccessor.Principal.AddIdentity(new ClaimsIdentity(mapClaims.Select(p => new Claim(map[p.Type], p.Value, p.ValueType, p.Issuer))));
await next();
});
app.UseAbpRequestLocalization(); //TODO: localization?
app.UseSwagger();
app.UseSwaggerUI(options =>

4
samples/MicroserviceDemo/microservices/ProductService.Host/ProductServiceHostModule.cs

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Builder;
@ -120,10 +120,12 @@ namespace ProductService.Host
currentPrincipalAccessor.Principal.AddIdentity(new ClaimsIdentity(mapClaims.Select(p => new Claim(map[p.Type], p.Value, p.ValueType, p.Issuer))));
await next();
});
if (MsDemoConsts.IsMultiTenancyEnabled)
{
app.UseMultiTenancy();
}
app.UseAbpRequestLocalization(); //TODO: localization?
app.UseSwagger();
app.UseSwaggerUI(options =>

4
samples/MicroserviceDemo/microservices/TenantManagementService.Host/TenantManagementServiceHostModule.cs

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Builder;
@ -121,10 +121,12 @@ namespace TenantManagementService.Host
currentPrincipalAccessor.Principal.AddIdentity(new ClaimsIdentity(mapClaims.Select(p => new Claim(map[p.Type], p.Value, p.ValueType, p.Issuer))));
await next();
});
if (MsDemoConsts.IsMultiTenancyEnabled)
{
app.UseMultiTenancy();
}
app.UseAbpRequestLocalization(); //TODO: localization?
app.UseSwagger();
app.UseSwaggerUI(options =>

2
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Client/MyCompanyName.MyProjectName.HttpApi.Client.csproj

@ -3,7 +3,7 @@
<Import Project="..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>MyCompanyName.MyProjectName</RootNamespace>
</PropertyGroup>

6
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
@ -179,12 +179,14 @@ namespace MyCompanyName.MyProjectName
app.UseRouting();
app.UseCors(DefaultCorsPolicyName);
app.UseAuthentication();
if (MultiTenancyConsts.IsEnabled)
{
app.UseMultiTenancy();
}
app.UseAuthorization();
app.UseAbpRequestLocalization();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(options =>

8
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -169,16 +169,16 @@ namespace MyCompanyName.MyProjectName
app.UseRouting();
app.UseCors(DefaultCorsPolicyName);
app.UseAuthentication();
app.UseJwtTokenMiddleware();
app.UseJwtTokenMiddleware();
if (MultiTenancyConsts.IsEnabled)
{
app.UseMultiTenancy();
}
app.UseAbpRequestLocalization();
app.UseIdentityServer();
app.UseAuthorization();
app.UseAbpRequestLocalization();
app.UseSwagger();
app.UseSwaggerUI(options =>

8
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using Localization.Resources.AbpUi;
@ -135,7 +135,7 @@ namespace MyCompanyName.MyProjectName
});
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
@ -155,13 +155,15 @@ namespace MyCompanyName.MyProjectName
app.UseRouting();
app.UseCors(DefaultCorsPolicyName);
app.UseAuthentication();
if (MultiTenancyConsts.IsEnabled)
{
app.UseMultiTenancy();
}
app.UseAbpRequestLocalization();
app.UseIdentityServer();
app.UseAuthorization();
app.UseAbpRequestLocalization();
app.UseAuditing();
app.UseAbpSerilogEnrichers();
app.UseConfiguredEndpoints();

8
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Microsoft.AspNetCore.Builder;
@ -67,7 +67,7 @@ namespace MyCompanyName.MyProjectName.Web
);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
@ -232,10 +232,8 @@ namespace MyCompanyName.MyProjectName.Web
app.UseMultiTenancy();
}
app.UseAuthorization();
app.UseAbpRequestLocalization();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(options =>

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore;
@ -211,9 +211,10 @@ namespace MyCompanyName.MyProjectName.Web
{
app.UseMultiTenancy();
}
app.UseAbpRequestLocalization();
app.UseIdentityServer();
app.UseAuthorization();
app.UseAbpRequestLocalization();
app.UseSwagger();
app.UseSwaggerUI(options =>
{

6
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/Index.cshtml

@ -1,6 +1,10 @@
@page
@inherits MyCompanyName.MyProjectName.Web.Pages.MyProjectNamePage
@using Microsoft.AspNetCore.Mvc.Localization
@using MyCompanyName.MyProjectName.Localization
@using Volo.Abp.Users
@model MyCompanyName.MyProjectName.Web.Pages.IndexModel
@inject IHtmlLocalizer<MyProjectNameResource> L
@inject ICurrentUser CurrentUser
@section styles {
<abp-style-bundle>
<abp-style src="/Pages/Index.css" />

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save