Browse Source

Merge branch 'dev' into liangshiwei/abp-template

pull/3932/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
dfa09e9e70
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      README.md
  2. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  3. 5
      docs/en/Samples/Index.md
  4. 2
      docs/en/SignalR-Integration.md
  5. 456
      docs/en/Text-Templating.md
  6. 12
      docs/en/UI/Angular/Confirmation-Service.md
  7. 165
      docs/en/UI/Angular/List-Service.md
  8. 6
      docs/en/UI/Angular/Track-By-Service.md
  9. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Paginator.md
  10. 8
      docs/en/docs-nav.json
  11. BIN
      docs/en/images/hello-template.png
  12. BIN
      docs/en/images/multiple-file-template.png
  13. 3
      docs/zh-Hans/Samples/Index.md
  14. 227
      docs/zh-Hans/SignalR-Integration.md
  15. 2
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Paginator.md
  16. BIN
      docs/zh-Hans/images/signal-js-file.png
  17. BIN
      docs/zh-Hans/images/signalr-demo-chat.png
  18. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyAttributeDto.cs
  19. 21
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs
  20. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/PagerModel.cs
  21. 16
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JQueryFormScriptContributor.cs
  22. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JsTreeOptions.cs
  23. 26
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JsTreeStyleContributor.cs
  24. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js
  25. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs
  26. 29
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
  27. 22
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
  28. 95
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyAttributeDtoFactory.cs
  29. 0
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ICachedObjectExtensionsDtoService.cs
  30. 9
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/IExtensionPropertyAttributeDtoFactory.cs
  31. 4
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs
  32. 36
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/ObjectExtending/ObjectExtendingPropertyInfoExtensions.cs
  33. 58
      framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpHub.cs
  34. 19
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs
  35. 10
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs
  36. 13
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
  37. 29
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  38. 6
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Paginator.cshtml
  39. 4
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Paginator.cshtml.cs
  40. 7
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Paginator/Index.cshtml
  41. 4
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Paginator/Index.cshtml.cs
  42. 9
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Paginator/Index.js
  43. 8
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/TypeHelper_Tests.cs
  44. 4
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hant.json
  45. 3
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserLookupAppService.cs
  46. 11
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UserLookupSearchInputDto.cs
  47. 17
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserLookupAppService.cs
  48. 30
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json
  49. 19
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExternalUserLookupServiceProvider.cs
  50. 21
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs
  51. 19
      modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientIdentityUserLookupService.cs
  52. 8
      modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserLookupController.cs
  53. 35
      modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserLookupAppService_Tests.cs
  54. 9
      modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IExternalUserLookupServiceProvider.cs
  55. 7
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserLookupService.cs
  56. 8
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserRepository.cs
  57. 13
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupService.cs
  58. 20
      modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs
  59. 17
      modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs
  60. 3
      npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts
  61. 3
      npm/ng-packs/apps/dev-app/src/environments/environment.ts
  62. 28
      npm/ng-packs/package.json
  63. 2
      npm/ng-packs/packages/core/package.json
  64. 2
      npm/ng-packs/packages/core/src/lib/actions/session.actions.ts
  65. 2
      npm/ng-packs/packages/core/src/lib/models/config.ts
  66. 1
      npm/ng-packs/packages/core/src/lib/services/index.ts
  67. 101
      npm/ng-packs/packages/core/src/lib/services/list.service.ts
  68. 34
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  69. 66
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  70. 7
      npm/ng-packs/packages/core/src/lib/states/session.state.ts
  71. 14
      npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts
  72. 153
      npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts
  73. 8
      npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts
  74. 1
      npm/ng-packs/packages/core/src/lib/tokens/index.ts
  75. 3
      npm/ng-packs/packages/core/src/lib/tokens/list.token.ts
  76. 2
      npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts
  77. 5
      npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html
  78. 5
      npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.scss
  79. 63
      npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts
  80. 8
      npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts
  81. 7
      npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts
  82. 15
      npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts
  83. 141
      npm/ng-packs/yarn.lock
  84. 5
      npm/packs/jstree/abp.resourcemapping.js
  85. 12
      npm/packs/jstree/package.json
  86. 11
      templates/app/angular/src/environments/environment.prod.ts
  87. 11
      templates/app/angular/src/environments/environment.ts
  88. 6
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/Index.cshtml
  89. 16
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/MyProjectNamePage.cs
  90. 3
      templates/module/angular/src/environments/environment.prod.ts
  91. 3
      templates/module/angular/src/environments/environment.ts
  92. 17
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs

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 & Test](https://github.com/abpframework/abp/workflows/Main/badge.svg)](https://github.com/abpframework/abp/actions?query=workflow%3AMain)
[![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).

5
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,7 @@
"EmailSent": "Email Sent",
"SuccessfullySent": "Successfully Sent",
"SuccessfullyDeleted": "Successfully Deleted",
"DiscountRequestDeletionWarningMessage": "Discount request will be deleted"
"DiscountRequestDeletionWarningMessage": "Discount request will be deleted" ,
"BusinessType": "Business Type"
}
}

5
docs/en/Samples/Index.md

@ -37,7 +37,7 @@ While there is no Razor Pages & MongoDB combination, you can check both document
* [EF Core database migrations document](../Entity-Framework-Core-Migrations.md)
* **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo)
* [Signal Integration document](../SignalR-Integration.md)
* [SignalR Integration document](../SignalR-Integration.md)
* **Dashboard Demo**: A simple application to show how to use the widget system for the ASP.NET Core MVC UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [Widget documentation](../UI/AspNetCore/Widgets.md)
@ -45,6 +45,9 @@ While there is no Razor Pages & MongoDB combination, you can check both document
* [Source code](https://github.com/abpframework/abp-samples/tree/master/RabbitMqEventBus)
* [Distributed event bus document](../Distributed-Event-Bus.md)
* [RabbitMQ distributed event bus integration document](../Distributed-Event-Bus-RabbitMQ-Integration.md)
* **Text Templates Demo**: Shows different use cases of the text templating system.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo)
* [Text templating documentation](../Text-Templating.md)
* **Authentication Customization**: A solution to show how to customize the authentication for ASP.NET Core MVC / Razor Pages applications.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)
* Related "[How To](../How-To/Index.md)" documents:

2
docs/en/SignalR-Integration.md

@ -25,7 +25,7 @@ If you want to manually install;
1. Add the [Volo.Abp.AspNetCore.SignalR](https://www.nuget.org/packages/Volo.Abp.AspNetCore.SignalR) NuGet package to your project:
```
Install-Package Volo.Abp.BackgroundJobs.HangFire
Install-Package Volo.Abp.AspNetCore.SignalR
```
Or use the Visual Studio NuGet package management UI to install it.

456
docs/en/Text-Templating.md

@ -1,3 +1,455 @@
# Text-Templating
# Text Templating
TODO
## Introduction
ABP Framework provides a simple, yet efficient text template system. Text templating is used to dynamically render contents based on a template and a model (a data object):
***TEMPLATE + MODEL ==render==> RENDERED CONTENT***
It is very similar to an ASP.NET Core Razor View (or Page):
*RAZOR VIEW (or PAGE) + MODEL ==render==> HTML CONTENT*
You can use the rendered output for any purpose, like sending emails or preparing some reports.
### Example
Here, a simple template:
````
Hello {%{{{model.name}}}%} :)
````
You can define a class with a `Name` property to render this template:
````csharp
public class HelloModel
{
public string Name { get; set; }
}
````
If you render the template with a `HelloModel` with the `Name` is `John`, the rendered output is will be:
````
Hello John :)
````
Template rendering engine is very powerful;
* It is based on the [Scriban](https://github.com/lunet-io/scriban) library, so it supports **conditional logics**, **loops** and much more.
* Template content **can be localized**.
* You can define **layout templates** to be used as the layout while rendering other templates.
* You can pass arbitrary objects to the template context (beside the model) for advanced scenarios.
### Source Code
Get [the source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document.
## Installation
It is suggested to use the [ABP CLI](CLI.md) to install this package.
### Using the ABP CLI
Open a command line window in the folder of the project (.csproj file) and type the following command:
````bash
abp add-package Volo.Abp.TextTemplating
````
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.TextTemplating](https://www.nuget.org/packages/Volo.Abp.TextTemplating) NuGet package to your project:
````
Install-Package Volo.Abp.TextTemplating
````
2. Add the `AbpTextTemplatingModule` to the dependency list of your module:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpTextTemplatingModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## Defining Templates
Before rendering a template, you should define it. Create a class inheriting from the `TemplateDefinitionProvider` base class:
````csharp
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Hello") //template name: "Hello"
.WithVirtualFilePath(
"/Demos/Hello/Hello.tpl", //template content path
isInlineLocalized: true
)
);
}
}
````
* `context` object is used to add new templates or get the templates defined by depended modules. Used `context.Add(...)` to define a new template.
* `TemplateDefinition` is the class represents a template. Each template must have a unique name (that will be used while you are rendering the template).
* `/Demos/Hello/Hello.tpl` is the path of the template file.
* `isInlineLocalized` is used to declare if you are using a single template for all languages (`true`) or different templates for each language (`false`). See the Localization section below for more.
### The Template Content
`WithVirtualFilePath` indicates that we are using the [Virtual File System](Virtual-File-System.md) to store the template content. Create a `Hello.tpl` file inside your project and mark it as "**embedded resource**" on the properties window:
![hello-template](images/hello-template.png)
Example `Hello.tpl` content is shown below:
````
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:
````csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo");
});
````
* `TextTemplateDemoModule` is the module class that you define your template in.
* `TextTemplateDemo` is the root namespace of your project.
## Rendering the Template
`ITemplateRenderer` service is used to render a template content.
### Example: Rendering a Simple Template
````csharp
public class HelloDemo : ITransientDependency
{
private readonly ITemplateRenderer _templateRenderer;
public HelloDemo(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task RunAsync()
{
var result = await _templateRenderer.RenderAsync(
"Hello", //the template name
new HelloModel
{
Name = "John"
}
);
Console.WriteLine(result);
}
}
````
* `HelloDemo` is a simple class that injects the `ITemplateRenderer` in its constructor and uses it inside the `RunAsync` method.
* `RenderAsync` gets two fundamental parameters:
* `templateName`: The name of the template to be rendered (`Hello` in this example).
* `model`: An object that is used as the `model` inside the template (a `HelloModel` object in this example).
The result shown below for this example:
````csharp
Hello John :)
````
### Anonymous Model
While it is suggested to create model classes for the templates, it would be practical (and possible) to use anonymous objects for simple cases:
````csharp
var result = await _templateRenderer.RenderAsync(
"Hello",
new
{
Name = "John"
}
);
````
In this case, we haven't created a model class, but created an anonymous object as the model.
### PascalCase vs camelCase
PascalCase property names (like `UserName`) is used as camelCase (like `userName`) in the templates.
## Localization
It is possible to localize a template content based on the current culture. There are two types of localization options described in the following sections.
### Inline localization
Inline localization uses the [localization system](Localization.md) to localize texts inside templates.
#### Example: Reset Password Link
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>
````
`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:
````json
"ResetMyPassword": "Click here to reset your password"
````
You also need to declare the localization resource to be used with this template, inside your template definition provider class:
````csharp
context.Add(
new TemplateDefinition(
"PasswordReset", //Template name
typeof(DemoResource) //LOCALIZATION RESOURCE
).WithVirtualFilePath(
"/Demos/PasswordReset/PasswordReset.tpl", //template content path
isInlineLocalized: true
)
);
````
That's all. When you render this template like that:
````csharp
var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name
new PasswordResetModel
{
Link = "https://abp.io/example-link?userId=123&token=ABC"
}
);
````
You will see the localized result:
````csharp
<a href="https://abp.io/example-link?userId=123&token=ABC">Click here to reset your password</a>
````
> If you define the [default localization resource](Localization.md) for your application, then no need to declare the resource type for the template definition.
### Multiple Contents Localization
Instead of a single template that uses the localization system to localize the template, you may want to create different template files for each language. It can be needed if the template should be completely different for a specific culture rather than simple text localizations.
#### Example: Welcome Email Template
Assuming that you want to send a welcome email to your users, but want to define a completely different template based on the user culture.
First, create a folder and put your templates inside it, like `en.tpl`, `tr.tpl`... one for each culture you support:
![multiple-file-template](images/multiple-file-template.png)
Then add your template definition in the template definition provider class:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en"
)
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates", //template content folder
isInlineLocalized: false
)
);
````
* Set **default culture name**, so it fallbacks to the default culture if there is no template for the desired culture.
* Specify **the template folder** rather than a single template file.
* Set `isInlineLocalized` to `false` for this case.
That's all, you can render the template for the current culture:
````csharp
var result = await _templateRenderer.RenderAsync("WelcomeEmail");
````
> Skipped the modal for this example to keep it simple, but you can use models as just explained before.
### Specify the Culture
`ITemplateRenderer` service uses the current culture (`CultureInfo.CurrentUICulture`) if not specified. If you need, you can specify the culture as the `cultureName` parameter:
````csharp
var result = await _templateRenderer.RenderAsync(
"WelcomeEmail",
cultureName: "en"
);
````
## Layout Templates
Layout templates are used to create shared layouts among other templates. It is similar to the layout system in the ASP.NET Core MVC / Razor Pages.
### Example: Email HTML Layout Template
For example, you may want to create a single layout for all of your email templates.
First, create a template file just like before:
````xml
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
{%{{{content}}}%}
</body>
</html>
````
* 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:
````csharp
context.Add(
new TemplateDefinition(
"EmailLayout",
isLayout: true //SET isLayout!
).WithVirtualFilePath(
"/Demos/EmailLayout/EmailLayout.tpl",
isInlineLocalized: true
)
);
````
Now, you can use this template as the layout of any other template:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en",
layout: "EmailLayout" //Set the LAYOUT
).WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates",
isInlineLocalized: false
)
);
````
## Global Context
ABP passes the `model` that can be used to access to the model inside the template. You can pass more global variables if you need.
An example template content:
````
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:
````csharp
var result = await _templateRenderer.RenderAsync(
"GlobalContextUsage",
globalContext: new Dictionary<string, object>
{
{"myGlobalObject", "TEST VALUE"}
}
);
````
The rendering result will be:
````
A global object value: TEST VALUE
````
## Advanced Features
This section covers some internals and more advanced usages of the text templating system.
### Template Content Provider
`ITemplateRenderer` is used to render the template, which is what you want for most of the cases. However, you can use the `ITemplateContentProvider` to get the raw (not rendered) template contents.
> `ITemplateContentProvider` is internally used by the `ITemplateRenderer` to get the raw template contents.
Example:
````csharp
public class TemplateContentDemo : ITransientDependency
{
private readonly ITemplateContentProvider _templateContentProvider;
public TemplateContentDemo(ITemplateContentProvider templateContentProvider)
{
_templateContentProvider = templateContentProvider;
}
public async Task RunAsync()
{
var result = await _templateContentProvider
.GetContentOrNullAsync("Hello");
Console.WriteLine(result);
}
}
````
The result will be the raw template content:
````
Hello {%{{{model.name}}}%} :)
````
* `GetContentOrNullAsync` returns `null` if no content defined for the requested template.
* It can get a `cultureName` parameter that is used if template has different files for different cultures (see Multiple Contents Localization section above).
### Template Content Contributor
`ITemplateContentProvider` service uses `ITemplateContentContributor` implementations to find template contents. There is a single pre-implemented content contributor, `VirtualFileTemplateContentContributor`, which gets template contents from the virtual file system as described above.
You can implement the `ITemplateContentContributor` to read raw template contents from another source.
Example:
````csharp
public class MyTemplateContentProvider
: ITemplateContentContributor, ITransientDependency
{
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
var templateName = context.TemplateDefinition.Name;
//TODO: Try to find content from another source
return null;
}
}
````
Return `null` if your source can not find the content, so `ITemplateContentProvider` fallbacks to the next contributor.
### Template Definition Manager
`ITemplateDefinitionManager` service can be used to get the template definitions (created by the template definition providers).
## See Also
* [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document.
* [Localization system](Localization.md).
* [Virtual File System](Virtual-File-System.md).

12
docs/en/UI/Angular/Confirmation-Service.md

@ -53,7 +53,7 @@ this.confirmation
- `Confirmation.Status` is an enum and has three properties;
- `Confirmation.Status.confirm` is a closing event value that will be emitted when the popup is closed by the confirm button.
- `Confirmation.Status.reject` is a closing event value that will be emitted when the popup is closed by the cancel button.
- `Confirmation.Status.dismiss` is a closing event value that will be emitted when the popup is closed by pressing the escape.
- `Confirmation.Status.dismiss` is a closing event value that will be emitted when the popup is closed by pressing the escape or clicking the backdrop.
If you are not interested in the confirmation status, you do not have to subscribe to the returned observable:
@ -70,6 +70,7 @@ Options can be passed as the third parameter to `success`, `warn`, `error`, and
const options: Partial<Confirmation.Options> = {
hideCancelBtn: false,
hideYesBtn: false,
dismissible: false,
cancelText: 'Close',
yesText: 'Confirm',
messageLocalizationParams: ['Demo'],
@ -83,10 +84,11 @@ this.confirmation.warn(
);
```
- `hideCancelBtn` option hides the cancellation button when `true`. Default value is `false`
- `hideYesBtn` option hides the confirmation button when `true`. Default value is `false`
- `cancelText` is the text of the cancellation button. A localization key or localization object can be passed. Default value is `AbpUi::Cancel`
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`
- `hideCancelBtn` option hides the cancellation button when `true`. Default value is `false`.
- `hideYesBtn` option hides the confirmation button when `true`. Default value is `false`.
- `dismissible` option allows dismissing the confirmation popup by pressing escape or clicking the backdrop. Default value is `true`.
- `cancelText` is the text of the cancellation button. A localization key or localization object can be passed. Default value is `AbpUi::Cancel`.
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`.
- `messageLocalizationParams` is the interpolation parameters for the localization of the message.
- `titleLocalizationParams` is the interpolation parameters for the localization of the title.

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)

2
docs/en/UI/AspNetCore/Tag-Helpers/Paginator.md

@ -24,7 +24,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components
public void OnGet(int currentPage, string sort)
{
PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort);
PagerModel = new PagerModel(100, 10, currentPage, 10, "/Components/Paginator", sort);
}
}
}

8
docs/en/docs-nav.json

@ -179,6 +179,10 @@
"text": "Object to object mapping",
"path": "Object-To-Object-Mapping.md"
},
{
"text": "Text Templating",
"path": "Text-Templating.md"
},
{
"text": "Object Serialization"
},
@ -379,6 +383,10 @@
{
"text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md"
},
{
"text": "ListService",
"path": "UI/Angular/List-Service.md"
}
]
},

BIN
docs/en/images/hello-template.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
docs/en/images/multiple-file-template.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

3
docs/zh-Hans/Samples/Index.md

@ -35,6 +35,9 @@
* **Entity Framework 迁移**: 演示如何将应用程序拆分为多个数据库的解决方案. 每个数据库包含不同的模块.
* [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [EF Core数据库迁移文档](../Entity-Framework-Core-Migrations.md)
* **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users.
* [源码](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo)
* [SignalR 集成文档](../SignalR-Integration.md)
* **Dashboard Demo**: 一个简单的应用程序,展示了如何在ASP.NET Core MVC UI中使用widget系统.
* [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [Widget 文档](../UI/AspNetCore/Widgets.md)

227
docs/zh-Hans/SignalR-Integration.md

@ -0,0 +1,227 @@
# SignalR 集成
> 你可以按照[标准的微软教程](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signal)添加[SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction)到你的应用程序,但ABP提供了简化集成的SignalR集成包.
## 安装
### 服务器端
建议使用[ABP CLI](CLI.md)安装包.
#### 使用 ABP CLI
在项目的文件夹(.csproj文件)中打开命令行窗口,然后输入以下命令:
```bash
abp add-package Volo.Abp.AspNetCore.SignalR
```
> 你通常需要将此软件包添加到应用程序的Web或API层,具体取决于你的架构.
#### 手动安装
如果你想手动安装:
1. 添加[Volo.Abp.AspNetCore.SignalR](https://www.nuget.org/packages/Volo.Abp.AspNetCore.SignalR)NuGet包到你的项目:
```
Install-Package Volo.Abp.AspNetCore.SignalR
```
或者使用VisualStudio提供的UI安装
2. 添加 `AbpAspNetCoreSignalRModule` 到你的模块的依赖列表.
```csharp
[DependsOn(
//...other dependencies
typeof(AbpAspNetCoreSignalRModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
```
> 你不需要 `services.AddSignalR()``app.UseEndpoints(...)`,它们在 `AbpAspNetCoreSignalRModule` 中已经添加了.
### 客户端
客户端安装取决于你的UI框架/客户端类型.
#### ASP.NET Core MVC / Razor Pages UI
在你的Web项目的根文件夹中运行以下命令:
````bash
yarn add @abp/signalr
````
> 需要 [yarn](https://yarnpkg.com/) 环境.
它会添加 `@abp/signalr` 到你的项目中的 `package.json` 依赖项:
````json
{
...
"dependencies": {
...
"@abp/signalr": "~2.7.0"
}
}
````
在你的Web项目的根文件夹中运行 `gulp`:
````bash
gulp
````
它会将SignalR JavaScript文件拷贝到你的项目:
![signal-js-file](images/signal-js-file.png)
最后将以下代码添加到页面/视图中, 添加包含 `signalr.js` 文件:
````xml
@section scripts {
<abp-script type="typeof(SignalRBrowserScriptContributor)" />
}
````
它需要将 `@using Volo.Abp.AspNetCore.Mvc.UI.Packages.SignalR` 添加到你的页面/视图.
> 你可以用标准方式添加 `signalr.js` 文件. 但是使用 `SignalRBrowserScriptContributor` 具有其他好处. 有关详细信息,请参见[客户端程序包管理](UI/AspNetCore/Client-Side-Package-Management.md)和[捆绑和压缩文档](UI/AspNetCore/Bundling-Minification.md).
这就是全部了,你可以在你的页面使用[SignalR JavaScript API](https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client).
#### 其他的UI框架/客户端
其他类型的客户端请参考[微软文档](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction).
## ABP框架集成
本节介绍了使用ABP框架集成包的其他好处.
### Hub 路由与Mapping
ABP自动将所有集线器注册到[依赖注入](Dependency-Injection.md)(做为transient)并映射集线器端点. 因此你不需要使用 `app.UseEndpoints(...)` 即可映射你的集线器.集线器路由(URL)通常是根据你的集线器名称确定.
示例:
````csharp
public class MessagingHub : Hub
{
//...
}
````
`MessasingHub` 集线器的路由为 `/signalr-hubs/messasing`:
* 添加了标准 `/signalr-hubs/` 前缀.
* 使用**驼峰命名**集线器名称,不包含 `Hub` 后缀.
如果你想指定路由,你可以使用 `HubRoute` attribute:
````csharp
[HubRoute("/my-messasing-hub")]
public class MessagingHub : Hub
{
//...
}
````
### AbpHub 基类
你可以从 `AbpHub``AbpHub<T>` 继承标准的 `Hub``Hub<T>` 类,它们具有实用的基本属性,如 `CurrentUser`.
示例:
````csharp
public class MessagingHub : AbpHub
{
public async Task SendMessage(string targetUserName, string message)
{
var currentUserName = CurrentUser.UserName; //Access to the current user info
var txt = L["MyText"]; //Localization
}
}
````
> 虽然可以将相同的属性注入到集线器构造函数中,但是这种方式简化了集线器类.
### 手动注册/Mapping
ABP会自动将所有集线器注册到[依赖注入](Dependency-Injection.md)作为**transient service**. 如果想要禁用集线器类**自动添加依赖注入**,只需要使用 `DisableConventionalRegistration` attribute. 如果愿意,你仍然可以在模块的 `ConfigureServices` 方法中注册集线器类:
````csharp
context.Services.AddTransient<MessagingHub>();
````
当**你或ABP**将类注册到依赖注入时,如前几节所述,它会自动映射到端点路由配置. 如果要手动映射集线器类,你可以使用 `DisableAutoHubMap` attribute.
对于手动映射,你有两个选择:
1. 使用 `AbpSignalROptions` 添加map配置(在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中),ABP会为集线器执行端点映射:
````csharp
Configure<AbpSignalROptions>(options =>
{
options.Hubs.Add(
new HubConfig(
typeof(MessagingHub), //Hub type
"/my-messaging/route", //Hub route (URL)
hubOptions =>
{
//Additional options
hubOptions.LongPolling.PollTimeout = TimeSpan.FromSeconds(30);
}
)
);
});
````
这是提供其他SignalR选项的好方式.
如果你不想禁用自动集线器map,但仍想执行其他SignalR配置,可以使用 `options.Hubs.AddOrUpdate(...)` 方法:
````csharp
Configure<AbpSignalROptions>(options =>
{
options.Hubs.AddOrUpdate(
typeof(MessagingHub), //Hub type
config => //Additional configuration
{
config.RoutePattern = "/my-messaging-hub"; //override the default route
config.ConfigureActions.Add(hubOptions =>
{
//Additional options
hubOptions.LongPolling.PollTimeout = TimeSpan.FromSeconds(30);
});
}
);
});
````
你可以通过这种方式修改在依赖模块(没有源代码访问权限)中定义的集线器类的选项.
2. 在[模块](Module-Development-Basics.md)的 `OnApplicationInitialization` 方法中更改 `app.UseConfiguredEndpoints`(添加了lambda方法作为参数).
````csharp
app.UseConfiguredEndpoints(endpoints =>
{
endpoints.MapHub<MessagingHub>("/my-messaging-hub", options =>
{
options.LongPolling.PollTimeout = TimeSpan.FromSeconds(30);
});
});
````
### UserIdProvider
ABP实现 `SignalR``IUserIdProvider` 接口,从ABP框架的 `ICurrentUser` 服务提供当前用户ID(请参阅[当前用户服务](CurrentUser.md)),它将集成到应用程序的身份验证系统中,实现类是 `AbpSignalRUserIdProvider` (如果你想更改/覆盖它).
## 示例应用程序
参阅 [SignalR集成Demo](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo),它有一个简单的聊天页面,可以在(经过身份验证的)用户之间发送消息.
![signalr-demo-chat](images/signalr-demo-chat.png)

2
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Paginator.md

@ -24,7 +24,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components
public void OnGet(int currentPage, string sort)
{
PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort);
PagerModel = new PagerModel(100, 10, currentPage, 10, "/Components/Paginator", sort);
}
}
}

BIN
docs/zh-Hans/images/signal-js-file.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/zh-Hans/images/signalr-demo-chat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

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

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

@ -1,4 +1,6 @@
using System.Text;
using System;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
@ -123,6 +125,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
var tagHelperOutput = await anchorTagHelper.ProcessAndGetOutputAsync(attributeList, context, "a", TagMode.StartTagAndEndTag);
SetHrefAttribute(currentPage, attributeList);
tagHelperOutput.Content.SetHtmlContent(localizer[localizationKey]);
var renderedHtml = tagHelperOutput.Render(_encoder);
@ -172,5 +176,20 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
" </ nav>\r\n" +
" </div>\r\n";
}
protected virtual void SetHrefAttribute(string currentPage, TagHelperAttributeList attributeList)
{
var hrefAttribute = attributeList.FirstOrDefault(x => x.Name.Equals("href", StringComparison.OrdinalIgnoreCase));
if (hrefAttribute != null)
{
var pageUrl = TagHelper.Model.PageUrl;
var routeValue = $"currentPage={currentPage}{(TagHelper.Model.Sort.IsNullOrWhiteSpace()? "" : "&sort="+TagHelper.Model.Sort)}";
pageUrl += pageUrl.Contains("?") ? "&" + routeValue : "?" + routeValue;
attributeList.Remove(hrefAttribute);
attributeList.Add(new TagHelperAttribute("href", pageUrl, hrefAttribute.ValueStyle));
}
}
}
}

7
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/PagerModel.cs

@ -38,7 +38,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
PageSize = pageSize;
TotalPageCount = (int)Math.Ceiling(Convert.ToDouble((decimal)TotalItemsCount / PageSize));
Sort = sort;
PageUrl = pageUrl;
PageUrl = pageUrl?.EnsureStartsWith('/') ?? "/";
if (currentPage > TotalPageCount)
{
@ -53,8 +54,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
CurrentPage = currentPage;
}
ShowingFrom = totalCount == 0 ? 0 : CurrentPage * PageSize;
ShowingTo = totalCount == 0 ? 0 : ShowingFrom + PageSize;
ShowingFrom = totalCount == 0 ? 0 : (CurrentPage - 1) * PageSize + 1;
ShowingTo = totalCount == 0 ? 0 : (int)Math.Min(ShowingFrom + PageSize - 1 , totalCount);
PreviousPage = CurrentPage <= 1 ? 1 : CurrentPage - 1;
NextPage = CurrentPage >= TotalPageCount ? CurrentPage : CurrentPage + 1;
Pages = CalculatePageNumbers();

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

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];

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

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

22
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,23 +10,29 @@ 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()
@ -137,7 +143,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
foreach (var attribute in propertyConfig.Attributes)
{
extensionPropertyDto.Attributes.Add(
ExtensionPropertyAttributeDto.Create(attribute)
ExtensionPropertyAttributeDtoFactory.Create(attribute)
);
}

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

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

@ -1,7 +1,9 @@
using System;
using System.ComponentModel;
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 +61,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
return value;
}
return Convert.ChangeType(value, propertyInfo.Type);
return TypeHelper.ConvertFromString(propertyInfo.Type, value);
}
}
}

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

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
@ -6,6 +7,31 @@ namespace Volo.Abp.ObjectExtending
{
public static class ObjectExtensionPropertyInfoAspNetCoreMvcExtensions
{
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(int?),
typeof(long?),
typeof(byte?),
typeof(sbyte?),
typeof(short?),
typeof(ushort?),
typeof(uint?),
typeof(long?),
typeof(ulong?),
typeof(float?),
typeof(double?),
};
public static string GetInputType(this ObjectExtensionPropertyInfo propertyInfo)
{
foreach (var attribute in propertyInfo.Attributes)
@ -79,15 +105,7 @@ namespace Volo.Abp.ObjectExtending
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))
if (NumberTypes.Contains(type))
{
return "number";
}

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

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

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
@ -264,5 +265,17 @@ 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)
{
return TypeDescriptor
.GetConverter(targetType)
.ConvertFromString(value);
}
}
}

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

6
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Paginator.cshtml

@ -47,7 +47,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components
public void OnGet(int currentPage, string sort)
{
PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort);
PagerModel = new PagerModel(100, 10, currentPage, 10, "/Components/Paginator", sort);
}
}
}
@ -60,7 +60,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class=&quot;row mt-3&quot;&gt;
&lt;div class=&quot;row mt-3&quot;&gt;
&lt;div class=&quot;col-sm-12 col-md-5&quot;&gt;
Showing 80 to 90 of 100 entries.
&lt;/div&gt;
@ -105,4 +105,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components
</abp-tab>
</abp-tabs>
</div>
</div>
</div>

4
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Paginator.cshtml.cs

@ -9,7 +9,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components
public void OnGet(int currentPage, string sort)
{
PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort);
PagerModel = new PagerModel(100, 10, currentPage, 10, "/Components/Paginator", sort);
}
}
}
}

7
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Paginator/Index.cshtml

@ -7,14 +7,9 @@
@{
PageLayout.Content.Title = "Paginator";
}
@section scripts {
<abp-script-bundle name="@typeof(IndexModel).FullName">
<abp-script src="/Pages/Components/Paginator/index.js" />
</abp-script-bundle>
}
<h2>Paginator</h2>
<p>Check the <a href="https://docs.abp.io/en/abp/latest/UI/AspNetCore/Tag-Helpers/Paginator" target="_blank">ABP Documentation</a>.</p>
@await Component.InvokeAsync(typeof(PaginatorDemoViewComponent), new { pagerModel = Model.PagerModel })
@await Component.InvokeAsync(typeof(PaginatorDemoViewComponent), new { pagerModel = Model.PagerModel })

4
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Paginator/Index.cshtml.cs

@ -9,7 +9,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.Pages.Components.Paginator
public void OnGet(int currentPage = 1, string sort = null)
{
PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort);
PagerModel = new PagerModel(100, 10, currentPage, 10, "/Components/Paginator", sort);
}
}
}
}

9
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Paginator/Index.js

@ -1,9 +0,0 @@
$(function () {
var links = $("a.page-link");
$.each(links, function (key, value) {
var oldUrl = links[key].getAttribute("href");
var value = Number(oldUrl.match(/currentPage=(\d+)&page/)[1]);
links[key].setAttribute("href", "/Components/Paginator?currentPage=" + value);
})
});

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

@ -75,6 +75,14 @@ namespace Volo.Abp.Reflection
TypeHelper.GetDefaultValue(typeof(string)).ShouldBeNull();
}
[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>
{

4
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hant.json

@ -42,6 +42,8 @@
"CreationTime": "建立時間",
"Description": "描述",
"Blogs": "部落格",
"Tags": "標籤"
"Tags": "標籤",
"ShareOn": "分享在",
"TitleLengthWarning": "為了優化搜索引擎,標題建議保持在60個字元以內"
}
}

3
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserLookupAppService.cs

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
@ -10,5 +11,7 @@ namespace Volo.Abp.Identity
Task<UserData> FindByIdAsync(Guid id);
Task<UserData> FindByUserNameAsync(string userName);
Task<ListResultDto<UserData>> SearchAsync(UserLookupSearchInputDto input);
}
}

11
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UserLookupSearchInputDto.cs

@ -0,0 +1,11 @@
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.Identity
{
public class UserLookupSearchInputDto : LimitedResultRequestDto, ISortedResultRequest
{
public string Sorting { get; set; }
public string Filter { get; set; }
}
}

17
modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserLookupAppService.cs

@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Users;
namespace Volo.Abp.Identity
@ -37,5 +39,20 @@ namespace Volo.Abp.Identity
return new UserData(userData);
}
public async Task<ListResultDto<UserData>> SearchAsync(UserLookupSearchInputDto input)
{
var users = await UserLookupServiceProvider.SearchAsync(
input.Sorting,
input.Filter,
input.MaxResultCount
);
return new ListResultDto<UserData>(
users
.Select(u => new UserData(u))
.ToList()
);
}
}
}

30
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json

@ -71,6 +71,34 @@
"Permission:Delete": "Silme",
"Permission:ChangePermissions": "İzinleri değiştirme",
"Permission:UserManagement": "Kullanıcı yönetimi",
"Permission:UserLookup": "Kullanıcı sorgulama"
"Permission:UserLookup": "Kullanıcı sorgulama",
"DisplayName:Abp.Identity.Password.RequiredLength": "Uzunluk gerekli",
"DisplayName:Abp.Identity.Password.RequiredUniqueChars": "Tekil karakter gerekli",
"DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "Alfasayısal olmayan karakter gerekli",
"DisplayName:Abp.Identity.Password.RequireLowercase": "Küçük harf gerekli",
"DisplayName:Abp.Identity.Password.RequireUppercase": "Büyük harf gerekli",
"DisplayName:Abp.Identity.Password.RequireDigit": "Rakam gerekli",
"DisplayName:Abp.Identity.Lockout.AllowedForNewUsers": "Yeni kullanıcılar için aktif",
"DisplayName:Abp.Identity.Lockout.LockoutDuration": "Kilitli kalma süresi (saniye)",
"DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts": "Maksimum başarısız giriş denemesi",
"DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail": "Onaylı e-posta gerekli",
"DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Telefon numarası onayını etkin",
"DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Onaylı telefon numarası gerekli",
"DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled": "Kullanıcı adı güncellenebilir",
"DisplayName:Abp.Identity.User.IsEmailUpdateEnabled": "E-posta güncellenebilir",
"Description:Abp.Identity.Password.RequiredLength": "Minimum parola uzunluğu.",
"Description:Abp.Identity.Password.RequiredUniqueChars": "Bir parolanın içermesi gereken minimum tekil karakter sayısı.",
"Description:Abp.Identity.Password.RequireNonAlphanumeric": "Parolaların alfasayısal olmayan bir karakter içermesi gerekiyorsa.",
"Description:Abp.Identity.Password.RequireLowercase": "Parolaların küçük harfli bir ASCII karakteri içermesi gerekiyorsa.",
"Description:Abp.Identity.Password.RequireUppercase": "Parolaların büyük harfli bir ASCII karakteri içermesi gerekiyorsa.",
"Description:Abp.Identity.Password.RequireDigit": "Parolaların bir rakam içermesi gerekiyorsa.",
"Description:Abp.Identity.Lockout.AllowedForNewUsers": "Yeni kullanıcılar kilitlenebilir.",
"Description:Abp.Identity.Lockout.LockoutDuration": "Kilitlenme olduğunda, ne kadar kilitli kalacağı.",
"Description:Abp.Identity.Lockout.MaxFailedAccessAttempts": "Kilitleme etkin olduğunda, kullanıcıya kilitlenmeden önce izin verilen başarısız giriş sayısı.",
"Description:Abp.Identity.SignIn.RequireConfirmedEmail": "Oturum açmak için onaylanmış bir e-posta adresinin gerekli olup olmadığı.",
"Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Oturum açmak için telefon numarası gerekli",
"Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Oturum açmak için onaylanmış bir telefon numarasının gerekli olup olmadığı.",
"Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Kullanıcı adının, kullanıcının kendisi tarafından güncellenebilirliği.",
"Description:Abp.Identity.User.IsEmailUpdateEnabled": "E-posta alanının, kullanıcının kendisi tarafından güncellenebilirliği"
}
}

19
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExternalUserLookupServiceProvider.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
@ -45,5 +47,22 @@ namespace Volo.Abp.Identity
)
)?.ToAbpUserData();
}
public virtual async Task<List<IUserData>> SearchAsync(
string sorting,
string filter,
int maxResultCount,
CancellationToken cancellationToken = default)
{
var users = await UserRepository.GetListAsync(
sorting: sorting,
maxResultCount: maxResultCount,
filter: filter,
includeDetails: false,
cancellationToken: cancellationToken
);
return users.Select(u => u.ToAbpUserData()).ToList();
}
}
}

21
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs

@ -19,7 +19,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<IdentityUser> FindByNormalizedUserNameAsync(
string normalizedUserName,
string normalizedUserName,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
@ -32,7 +32,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<List<string>> GetRoleNamesAsync(
Guid id,
Guid id,
CancellationToken cancellationToken = default)
{
var query = from userRole in DbContext.Set<IdentityUserRole>()
@ -44,8 +44,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<IdentityUser> FindByLoginAsync(
string loginProvider,
string providerKey,
string loginProvider,
string providerKey,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
@ -77,7 +77,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
string normalizedRoleName,
string normalizedRoleName,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
@ -97,10 +97,10 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<List<IdentityUser>> GetListAsync(
string sorting = null,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
int skipCount = 0,
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
@ -110,7 +110,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
!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))
.PageBy(skipCount, maxResultCount)
@ -131,7 +132,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<long> GetCountAsync(
string filter = null,
string filter = null,
CancellationToken cancellationToken = default)
{
return await this.WhereIf(

19
modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientIdentityUserLookupService.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
@ -25,5 +27,22 @@ namespace Volo.Abp.Identity
{
return await UserLookupAppService.FindByUserNameAsync(userName);
}
public async Task<List<IUserData>> SearchAsync(
string sorting,
string filter,
int maxResultCount,
CancellationToken cancellationToken = default)
{
var result = await UserLookupAppService.SearchAsync(
new UserLookupSearchInputDto
{
Filter = filter,
MaxResultCount = maxResultCount
}
);
return result.Items.Cast<IUserData>().ToList();
}
}
}

8
modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserLookupController.cs

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Users;
@ -32,5 +33,12 @@ namespace Volo.Abp.Identity
{
return LookupAppService.FindByUserNameAsync(userName);
}
[HttpGet]
[Route("search")]
public Task<ListResultDto<UserData>> SearchAsync(UserLookupSearchInputDto input)
{
return LookupAppService.SearchAsync(input);
}
}
}

35
modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserLookupAppService_Tests.cs

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Shouldly;
@ -52,5 +50,38 @@ namespace Volo.Abp.Identity
var user = await _identityUserLookupAppService.FindByUserNameAsync(Guid.NewGuid().ToString());
user.ShouldBeNull();
}
[Fact]
public async Task Search_Without_Filter_And_Sorting()
{
var result = await _identityUserLookupAppService.SearchAsync(new UserLookupSearchInputDto());
result.Items.Count.ShouldBeGreaterThanOrEqualTo(3);
result.Items.ShouldContain(u => u.UserName == "john.nash");
}
[Fact]
public async Task Search_With_Filter()
{
var result = await _identityUserLookupAppService.SearchAsync(
new UserLookupSearchInputDto
{
Filter = "a"
}
);
result.Items.Count.ShouldBeGreaterThanOrEqualTo(2);
result.Items.ShouldContain(u => u.UserName == "john.nash");
result.Items.ShouldContain(u => u.UserName == "david");
result = await _identityUserLookupAppService.SearchAsync(
new UserLookupSearchInputDto
{
Filter = "neo"
}
);
result.Items.Count.ShouldBeGreaterThanOrEqualTo(1);
result.Items.ShouldContain(u => u.UserName == "neo");
}
}
}

9
modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IExternalUserLookupServiceProvider.cs

@ -1,13 +1,20 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.Users
{
public interface IExternalUserLookupServiceProvider //TODO: Consider to inherit from IUserLookupService
public interface IExternalUserLookupServiceProvider
{
Task<IUserData> FindByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<IUserData> FindByUserNameAsync(string userName, CancellationToken cancellationToken = default);
Task<List<IUserData>> SearchAsync(
string sorting,
string filter,
int maxResultCount,
CancellationToken cancellationToken = default);
}
}

7
modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserLookupService.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -11,6 +12,10 @@ namespace Volo.Abp.Users
Task<TUser> FindByUserNameAsync(string userName, CancellationToken cancellationToken = default);
//TODO: More...
Task<List<IUserData>> SearchAsync(
string sorting,
string filter,
int maxResultCount,
CancellationToken cancellationToken = default);
}
}

8
modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserRepository.cs

@ -13,5 +13,13 @@ namespace Volo.Abp.Users
Task<TUser> FindByUserNameAsync(string userName, CancellationToken cancellationToken = default);
Task<List<TUser>> GetListAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default);
Task<List<TUser>> SearchAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
CancellationToken cancellationToken = default
);
}
}

13
modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupService.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@ -138,6 +140,17 @@ namespace Volo.Abp.Users
return await _userRepository.FindAsync(externalUser.Id, cancellationToken: cancellationToken);
}
public async Task<List<IUserData>> SearchAsync(string sorting, string filter, int maxResultCount, CancellationToken cancellationToken = default)
{
if (ExternalUserLookupServiceProvider != null)
{
return await ExternalUserLookupServiceProvider.SearchAsync(sorting, filter, maxResultCount, cancellationToken);
}
var localUsers = await _userRepository.SearchAsync(sorting, maxResultCount, 0, filter, cancellationToken);
return localUsers.Cast<IUserData>().ToList();
}
protected abstract TUser CreateUser(IUserData externalUser);
private async Task WithNewUowAsync(Func<Task> func)

20
modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Dynamic.Core;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -28,5 +29,24 @@ namespace Volo.Abp.Users.EntityFrameworkCore
{
return await DbSet.Where(u => ids.Contains(u.Id)).ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<List<TUser>> SearchAsync(
string sorting = null,
int maxResultCount = Int32.MaxValue,
int skipCount = 0,
string filter = null,
CancellationToken cancellationToken = default)
{
return await DbSet
.WhereIf(
!filter.IsNullOrWhiteSpace(),
u =>
u.UserName.Contains(filter) ||
u.Email.Contains(filter)
)
.OrderBy(sorting ?? nameof(IUser.UserName))
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
}
}

17
modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
@ -29,5 +30,21 @@ namespace Volo.Abp.Users.MongoDB
{
return await GetMongoQueryable().Where(u => ids.Contains(u.Id)).ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<List<TUser>> SearchAsync(string sorting = null, int maxResultCount = Int32.MaxValue, int skipCount = 0, string filter = null,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.WhereIf<TUser, IMongoQueryable<TUser>>(
!filter.IsNullOrWhiteSpace(),
u =>
u.UserName.Contains(filter) ||
u.Email.Contains(filter)
)
.OrderBy(sorting ?? nameof(IUserData.UserName))
.As<IMongoQueryable<TUser>>()
.PageBy<TUser, IMongoQueryable<TUser>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
}
}

3
npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts

@ -19,7 +19,4 @@ export const environment = {
url: 'https://localhost:44305',
},
},
localization: {
defaultResourceName: 'MyProjectName',
},
};

3
npm/ng-packs/apps/dev-app/src/environments/environment.ts

@ -19,7 +19,4 @@ export const environment = {
url: 'https://localhost:44305',
},
},
localization: {
defaultResourceName: 'MyProjectName',
},
};

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

2
npm/ng-packs/packages/core/src/lib/actions/session.actions.ts

@ -2,7 +2,7 @@ import { ABP } from '../models';
export class SetLanguage {
static readonly type = '[Session] Set Language';
constructor(public payload: string) {}
constructor(public payload: string, public dispatchAppConfiguration?: boolean) {}
}
export class SetTenant {
static readonly type = '[Session] Set Tenant';

2
npm/ng-packs/packages/core/src/lib/models/config.ts

@ -16,7 +16,7 @@ export namespace Config {
hmr?: boolean;
oAuthConfig: AuthConfig;
apis: Apis;
localization: { defaultResourceName: string };
localization?: { defaultResourceName?: string };
}
export interface Application {

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

34
npm/ng-packs/packages/core/src/lib/services/localization.service.ts

@ -1,11 +1,12 @@
import { Injectable, NgZone, Optional, SkipSelf } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { Store, Actions, ofActionSuccessful } from '@ngxs/store';
import { Actions, ofActionSuccessful, Store } from '@ngxs/store';
import { noop, Observable } from 'rxjs';
import { SetLanguage } from '../actions/session.actions';
import { ApplicationConfiguration } from '../models/application-configuration';
import { Config } from '../models/config';
import { ConfigState } from '../states/config.state';
import { registerLocale } from '../utils/initial-utils';
import { Config } from '../models/config';
import { SetLanguage } from '../actions/session.actions';
type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean;
@ -75,4 +76,31 @@ export class LocalizationService {
instant(key: string | Config.LocalizationWithDefault, ...interpolateParams: string[]): string {
return this.store.selectSnapshot(ConfigState.getLocalization(key, ...interpolateParams));
}
isLocalized(key, sourceName) {
if (sourceName === '_') {
// A convention to suppress the localization
return true;
}
const localization = this.store.selectSnapshot(
ConfigState.getOne('localization'),
) as ApplicationConfiguration.Localization;
sourceName = sourceName || localization.defaultResourceName;
if (!sourceName) {
return false;
}
const source = localization.values[sourceName];
if (!source) {
return false;
}
const value = source[key];
if (value === undefined) {
return false;
}
return true;
}
}

66
npm/ng-packs/packages/core/src/lib/states/config.state.ts

@ -155,33 +155,45 @@ export class ConfigState {
}
const keys = key.split('::') as string[];
const selector = createSelector([ConfigState], (state: Config.State) => {
if (!state.localization) return defaultValue || key;
const defaultResourceName = snq(() => state.environment.localization.defaultResourceName);
if (keys[0] === '') {
if (!defaultResourceName) {
throw new Error(
`Please check your environment. May you forget set defaultResourceName?
Here is the example:
{ production: false,
localization: {
defaultResourceName: 'MyProjectName'
}
}`,
);
}
const selector = createSelector([ConfigState], (state: Config.State): string => {
const warn = (message: string) => {
if (!state.environment.production) console.warn(message);
};
keys[0] = defaultResourceName;
if (keys.length < 2) {
warn('The localization source separator (::) not found.');
return defaultValue || (key as string);
}
if (!state.localization) return defaultValue || keys[1];
let localization = (keys as any).reduce((acc, val) => {
if (acc) {
return acc[val];
}
const sourceName =
keys[0] ||
snq(() => state.environment.localization.defaultResourceName) ||
state.localization.defaultResourceName;
const sourceKey = keys[1];
return undefined;
}, state.localization.values);
if (sourceName === '_') {
return defaultValue || sourceKey;
}
if (!sourceName) {
warn(
'Localization source name is not specified and the defaultResourceName was not defined!',
);
return defaultValue || sourceKey;
}
const source = state.localization.values[sourceName];
if (!source) {
warn('Could not find localization source: ' + sourceName);
return defaultValue || sourceKey;
}
let localization = source[sourceKey];
if (typeof localization === 'undefined') {
return defaultValue || sourceKey;
}
interpolateParams = interpolateParams.filter(params => params != null);
if (localization && interpolateParams && interpolateParams.length) {
@ -191,7 +203,7 @@ export class ConfigState {
}
if (typeof localization !== 'string') localization = '';
return localization || defaultValue || key;
return localization || defaultValue || (key as string);
});
return selector;
@ -219,9 +231,13 @@ export class ConfigState {
defaultLang = defaultLang.split(';')[0];
}
document.documentElement.setAttribute(
'lang',
configuration.localization.currentCulture.cultureName,
);
return this.store.selectSnapshot(SessionState.getLanguage)
? of(null)
: dispatch(new SetLanguage(defaultLang));
: dispatch(new SetLanguage(defaultLang, false));
}),
catchError(err => {
dispatch(new RestOccurError(new HttpErrorResponse({ status: 0, error: err })));

7
npm/ng-packs/packages/core/src/lib/states/session.state.ts

@ -69,12 +69,15 @@ export class SessionState {
}
@Action(SetLanguage)
setLanguage({ patchState, dispatch }: StateContext<Session.State>, { payload }: SetLanguage) {
setLanguage(
{ patchState, dispatch }: StateContext<Session.State>,
{ payload, dispatchAppConfiguration = true }: SetLanguage,
) {
patchState({
language: payload,
});
return dispatch(new GetAppConfiguration());
if (dispatchAppConfiguration) return dispatch(new GetAppConfiguration());
}
@Action(SetTenant)

14
npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts

@ -272,7 +272,7 @@ describe('ConfigState', () => {
);
expect(ConfigState.getLocalization('AbpIdentity::NoIdentity')(CONFIG_STATE_DATA)).toBe(
'AbpIdentity::NoIdentity',
'NoIdentity',
);
expect(
@ -287,18 +287,15 @@ describe('ConfigState', () => {
)(CONFIG_STATE_DATA),
).toBe('first and second do not match.');
try {
expect(
ConfigState.getLocalization('::Test')({
...CONFIG_STATE_DATA,
environment: {
...CONFIG_STATE_DATA.environment,
localization: {} as any,
},
});
expect(false).toBeTruthy(); // fail
} catch (error) {
expect((error as Error).message).toContain('Please check your environment');
}
}),
).toBe('Test');
});
});
@ -309,6 +306,7 @@ describe('ConfigState', () => {
const configuration = {
setting: { values: { 'Abp.Localization.DefaultLanguage': 'tr;TR' } },
localization: { currentCulture: {} },
};
const res$ = new ReplaySubject(1);
@ -327,7 +325,7 @@ describe('ConfigState', () => {
timer(0).subscribe(() => {
expect(patchStateArg).toEqual(configuration);
expect(dispatchArg instanceof SetLanguage).toBeTruthy();
expect(dispatchArg).toEqual({ payload: 'tr' });
expect(dispatchArg).toEqual({ payload: 'tr', dispatchAppConfiguration: false });
done();
});
});

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

8
npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts

@ -44,13 +44,7 @@ describe('LocalizationService', () => {
describe('#instant', () => {
it('should be return a localization', () => {
store.selectSnapshot.andCallFake(
(selector: (state: any, ...states: any[]) => Observable<string>) => {
return selector({
ConfigState: { getLocalization: (keys, ...interpolateParams) => keys },
});
},
);
store.selectSnapshot.andReturn('AbpTest');
expect(service.instant('AbpTest')).toBe('AbpTest');
});

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/confirmation/confirmation.component.html

@ -1,5 +1,8 @@
<div class="confirmation" *ngIf="confirmation$ | async as data">
<div class="confirmation-backdrop"></div>
<div
class="confirmation-backdrop"
(click)="data.options?.dismissible ? close(dismiss) : null"
></div>
<div class="confirmation-dialog">
<div class="icon-container" [ngClass]="data.severity" *ngIf="data.severity">
<i class="fa icon" [ngClass]="getIconClass(data)"></i>

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

8
npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts

@ -3,13 +3,19 @@ import { Config } from '@abp/ng.core';
export namespace Confirmation {
export interface Options {
id?: any;
closable?: boolean;
dismissible?: boolean;
messageLocalizationParams?: string[];
titleLocalizationParams?: string[];
hideCancelBtn?: boolean;
hideYesBtn?: boolean;
cancelText?: Config.LocalizationParam;
yesText?: Config.LocalizationParam;
/**
*
* @deprecated To be deleted in v2.9
*/
closable?: boolean;
}
export interface DialogData {

7
npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts

@ -68,7 +68,7 @@ export class ConfirmationService {
message: Config.LocalizationParam,
title: Config.LocalizationParam,
severity?: Confirmation.Severity,
options?: Partial<Confirmation.Options>,
options = {} as Partial<Confirmation.Options>,
): Observable<Confirmation.Status> {
if (!this.containerComponentRef) this.setContainer();
@ -78,8 +78,11 @@ export class ConfirmationService {
severity: severity || 'neutral',
options,
});
this.status$ = new Subject();
this.listenToEscape();
const { dismissible = true } = options;
if (dismissible) this.listenToEscape();
return this.status$;
}

15
npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts

@ -105,6 +105,21 @@ describe('ConfirmationService', () => {
selectConfirmationElement<HTMLButtonElement>('button#cancel').click();
});
test.each`
dismissible | count
${true} | ${1}
${false} | ${0}
`(
'should call the listenToEscape method $count times when dismissible is $dismissible',
({ dismissible, count }) => {
const spy = spyOn(service as any, 'listenToEscape');
service.info('', '', { dismissible });
expect(spy).toHaveBeenCalledTimes(count);
},
);
});
function clearElements(selector = '.confirmation') {

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

11
templates/app/angular/src/environments/environment.prod.ts

@ -2,7 +2,7 @@ export const environment = {
production: true,
application: {
name: 'MyProjectName',
logoUrl: ''
logoUrl: '',
},
oAuthConfig: {
issuer: 'https://localhost:44305',
@ -11,14 +11,11 @@ export const environment = {
scope: 'MyProjectName',
showDebugInformation: true,
oidc: false,
requireHttps: true
requireHttps: true,
},
apis: {
default: {
url: 'https://localhost:44305'
}
url: 'https://localhost:44305',
},
},
localization: {
defaultResourceName: 'MyProjectName'
}
};

11
templates/app/angular/src/environments/environment.ts

@ -2,7 +2,7 @@ export const environment = {
production: false,
application: {
name: 'MyProjectName',
logoUrl: ''
logoUrl: '',
},
oAuthConfig: {
issuer: 'https://localhost:44305',
@ -11,14 +11,11 @@ export const environment = {
scope: 'MyProjectName',
showDebugInformation: true,
oidc: false,
requireHttps: true
requireHttps: true,
},
apis: {
default: {
url: 'https://localhost:44305'
}
url: 'https://localhost:44305',
},
},
localization: {
defaultResourceName: 'MyProjectName'
}
};

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

16
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/MyProjectNamePage.cs

@ -1,16 +0,0 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using MyCompanyName.MyProjectName.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace MyCompanyName.MyProjectName.Web.Pages
{
/* Inherit your UI Pages from this class. To do that, add this line to your Pages (.cshtml files under the Page folder):
* @inherits MyCompanyName.MyProjectName.Web.Pages.MyProjectNamePage
*/
public abstract class MyProjectNamePage : AbpPage
{
[RazorInject]
public IHtmlLocalizer<MyProjectNameResource> L { get; set; }
}
}

3
templates/module/angular/src/environments/environment.prod.ts

@ -18,7 +18,4 @@ export const environment = {
url: 'https://localhost:44300',
},
},
localization: {
defaultResourceName: 'MyProjectName',
},
};

3
templates/module/angular/src/environments/environment.ts

@ -18,7 +18,4 @@ export const environment = {
url: 'https://localhost:44300',
},
},
localization: {
defaultResourceName: 'MyProjectName',
},
};

17
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Claims;
using IdentityModel;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
@ -171,7 +173,20 @@ namespace MyCompanyName.MyProjectName
{
app.UseMultiTenancy();
}
app.Use(async (ctx, next) =>
{
var currentPrincipalAccessor = ctx.RequestServices.GetRequiredService<ICurrentPrincipalAccessor>();
var map = new Dictionary<string, string>()
{
{ "sub", AbpClaimTypes.UserId },
{ "role", AbpClaimTypes.Role },
{ "email", AbpClaimTypes.Email },
//any other map
};
var mapClaims = currentPrincipalAccessor.Principal.Claims.Where(p => map.Keys.Contains(p.Type)).ToList();
currentPrincipalAccessor.Principal.AddIdentity(new ClaimsIdentity(mapClaims.Select(p => new Claim(map[p.Type], p.Value, p.ValueType, p.Issuer))));
await next();
});
app.UseAbpRequestLocalization();
app.UseAuthorization();
app.UseSwagger();

Loading…
Cancel
Save