Browse Source

merged dev

pull/3732/head
Galip Tolga Erdem 6 years ago
parent
commit
d22c8bfd44
  1. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  2. 10
      docs/en/CLI.md
  3. 21
      docs/en/Dapper.md
  4. 8
      docs/en/Object-Extensions.md
  5. 14
      docs/en/SignalR-Integration.md
  6. 8
      docs/en/Text-Templating.md
  7. 4
      docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md
  8. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/EntityExtensionDto.cs
  9. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionEnumDto.cs
  10. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionEnumFieldDto.cs
  11. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ObjectExtensionsDto.cs
  12. 21
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperExtensions.cs
  13. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs
  14. 56
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs
  15. 95
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs
  16. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs
  17. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerStyleContributor.cs
  18. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Luxon/LuxonScriptContributor.cs
  19. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
  20. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs
  21. 30
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  22. 100
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js
  23. 150
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs
  24. 11
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ActionResultHelper.cs
  25. 69
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
  26. 18
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs
  27. 1
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertiesDictionaryModelBinderProvider.cs
  28. 6
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs
  29. 6
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs
  30. 113
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/ObjectExtending/ObjectExtendingPropertyInfoExtensions.cs
  31. 17
      framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpSignalRUserIdProvider.cs
  32. 7
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/HttpContextCurrentPrincipalAccessor.cs
  33. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  34. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  35. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs
  36. 16
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Console/ConsoleTemplate.cs
  37. 13
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Console/ConsoleTemplateBase.cs
  38. 46
      framework/src/Volo.Abp.Core/Volo/Abp/AbpInitializationException.cs
  39. 27
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs
  40. 22
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
  41. 54
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
  42. 6
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
  43. 0
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs
  44. 0
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizationResourceNameAttribute.cs
  45. 50
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StringLocalizerHelper.cs
  46. 38
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/IBasicObjectExtensionPropertyInfo.cs
  47. 2
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfiguration.cs
  48. 32
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs
  49. 1
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs
  50. 2
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  51. 5
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ICurrentPrincipalAccessor.cs
  52. 29
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ThreadCurrentPrincipalAccessor.cs
  53. 50
      framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateLocalizer.cs
  54. 9
      framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs
  55. 4
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json
  56. 2
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/FakeAuthenticationMiddleware.cs
  57. 27
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs
  58. 64
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs
  59. 6
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs
  60. 8
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs
  61. 7
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/TypeHelper_Tests.cs
  62. 6
      framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json
  63. 20
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs
  64. 50
      framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/CurrentPrincipalAccessor_Test.cs
  65. 7
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json
  66. 7
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json
  67. 2
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl
  68. 21
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs
  69. 12
      modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs
  70. 8
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml
  71. 7
      modules/blogging/src/Volo.Blogging.Web/SocialMedia/BloggingTwitterOptions.cs
  72. 7
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/DocsAdminRemoteServiceConsts.cs
  73. 2
      modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.csproj
  74. 12
      modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo/Docs/Admin/DocsHttpApiClientModule.cs
  75. 7
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/DocsRemoteServiceConsts.cs
  76. 1
      modules/docs/src/Volo.Docs.HttpApi.Client/Volo.Docs.HttpApi.Client.csproj
  77. 13
      modules/docs/src/Volo.Docs.HttpApi.Client/Volo/Docs/DocsHttpApiClientModule.cs
  78. 2
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserLookupAppService.cs
  79. 7
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UserLookupCountInputDto.cs
  80. 4
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UserLookupSearchInputDto.cs
  81. 8
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserLookupAppService.cs
  82. 15
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExternalUserLookupServiceProvider.cs
  83. 7
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs
  84. 24
      modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientIdentityUserLookupService.cs
  85. 7
      modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserLookupController.cs
  86. 24
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs
  87. 12
      modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IExternalUserLookupServiceProvider.cs
  88. 21
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserLookupService.cs
  89. 4
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserRepository.cs
  90. 50
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupService.cs
  91. 22
      modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs
  92. 24
      modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs
  93. 56
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  94. 140
      npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts
  95. 176
      npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts
  96. 1
      npm/ng-packs/packages/core/src/lib/utils/index.ts
  97. 56
      npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts
  98. 8
      npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts
  99. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Client/MyCompanyName.MyProjectName.HttpApi.Client.csproj
  100. 10
      templates/app/aspnet-core/test/MyCompanyName.MyProjectName.TestBase/Security/FakeCurrentPrincipalAccessor.cs

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

@ -148,6 +148,10 @@
"SuccessfullySent": "Successfully Sent", "SuccessfullySent": "Successfully Sent",
"SuccessfullyDeleted": "Successfully Deleted", "SuccessfullyDeleted": "Successfully Deleted",
"DiscountRequestDeletionWarningMessage": "Discount request will be deleted" , "DiscountRequestDeletionWarningMessage": "Discount request will be deleted" ,
"BusinessType": "Business Type" "BusinessType": "Business Type",
"TotalQuestionCount": "Total question count",
"RemainingQuestionCount": "Remaining question count",
"TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !",
"QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !"
} }
} }

10
docs/en/CLI.md

@ -40,7 +40,7 @@ abp new Acme.BookStore
#### Options #### Options
* `--template` or `-t`: Specifies the template name. Default template name is `app`, which generates a web application. Available templates: * `--template` or `-t`: Specifies the template name. Default template name is `app`, which generates a web application. Available templates:
* `app` (default): [Application template](Startup-Templates/Application.md). Additional options: * **`app`** (default): [Application template](Startup-Templates/Application.md). Additional options:
* `--ui` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: * `--ui` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks:
* `mvc`: ASP.NET Core MVC. There are some additional options for this template: * `mvc`: ASP.NET Core MVC. There are some additional options for this template:
* `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios. * `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios.
@ -51,10 +51,10 @@ abp new Acme.BookStore
* `--mobile` or `-m`: Specifies the mobile application framework. Default framework is `react-native`. Available frameworks: * `--mobile` or `-m`: Specifies the mobile application framework. Default framework is `react-native`. Available frameworks:
* `none`: no mobile application. * `none`: no mobile application.
* `react-native`: React Native. * `react-native`: React Native.
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core. * `ef`: Entity Framework Core.
* `mongodb`: MongoDB. * `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). Additional options: * **`module`**: [Module template](Startup-Templates/Module.md). Additional options:
* `--no-ui`: Specifies to not include the UI. This makes possible to create service-only modules (a.k.a. microservices - without UI). * `--no-ui`: Specifies to not include the UI. This makes possible to create service-only modules (a.k.a. microservices - without UI).
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.

21
docs/en/Dapper.md

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

8
docs/en/Object-Extensions.md

@ -200,17 +200,17 @@ ObjectExtensionManager.Instance
````csharp ````csharp
ObjectExtensionManager.Instance ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, int>( .AddOrUpdateProperty<IdentityUser, DateTime>(
"MyIntProperty", "MyDateTimeProperty",
options => options =>
{ {
options.DefaultValueFactory = () => 42; options.DefaultValueFactory = () => DateTime.Now;
}); });
```` ````
`options.DefaultValueFactory` has a higher priority than the `options.DefaultValue` . `options.DefaultValueFactory` has a higher priority than the `options.DefaultValue` .
> Tip: Use `DefaultValueFactory` option only if the default value may change over the time. If it is a constant value, then use the `DefaultValue` option. > Tip: Use `DefaultValueFactory` option only if the default value may change over the time (like `DateTime.Now` in this example). If it is a constant value, then use the `DefaultValue` option.
#### CheckPairDefinitionOnMapping #### CheckPairDefinitionOnMapping

14
docs/en/SignalR-Integration.md

@ -1,6 +1,6 @@
# SignalR Integration # SignalR Integration
> It is already possible to follow [the standard Microsoft tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr) to add [SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) to your application. However, ABP provides a SignalR integration packages that simplify the integration and usage. > It is already possible to follow [the standard Microsoft tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr) to add [SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) to your application. However, ABP provides a SignalR integration packages those simplify the integration and usage.
## Installation ## Installation
@ -224,4 +224,14 @@ ABP implements SignalR's `IUserIdProvider` interface to provide the current user
See the [SignalR Integration Demo](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) as a sample application. It has a simple Chat page to send messages between (authenticated) users. See the [SignalR Integration Demo](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) as a sample application. It has a simple Chat page to send messages between (authenticated) users.
![signalr-demo-chat](images/signalr-demo-chat.png) ![signalr-demo-chat](images/signalr-demo-chat.png)
## Remarks
ABP Framework doesn't change the SignalR. It works in your ABP Framework based application just like any other ASP.NET Core application.
Refer to the Microsoft's documentation to [host and scale](https://docs.microsoft.com/en-us/aspnet/core/signalr/scale) your application, integrate to [Azure](https://docs.microsoft.com/en-us/aspnet/core/signalr/publish-to-azure-web-app) or [Redis backplane](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane)... etc.
## See Also
* [Microsoft SignalR documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction)

8
docs/en/Text-Templating.md

@ -204,13 +204,14 @@ Inline localization uses the [localization system](Localization.md) to localize
Assuming you need to send an email to a user to reset her/his password. Here, the template content: Assuming you need to send an email to a user to reset her/his password. Here, the template content:
```` ````
<a href="{%{{{model.link}}}%}">{%{{{L "ResetMyPassword"}}}%}</a> <a title="{%{{{L "ResetMyPasswordTitle"}}}%}" href="{%{{{model.link}}}%}">{%{{{L "ResetMyPassword" model.name}}}%}</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: `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 ````json
"ResetMyPassword": "Click here to reset your password" "ResetMyPasswordTitle": "Reset my password",
"ResetMyPassword": "Hi {0}, 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: You also need to declare the localization resource to be used with this template, inside your template definition provider class:
@ -234,6 +235,7 @@ var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name "PasswordReset", //the template name
new PasswordResetModel new PasswordResetModel
{ {
Name = "john",
Link = "https://abp.io/example-link?userId=123&token=ABC" Link = "https://abp.io/example-link?userId=123&token=ABC"
} }
); );
@ -242,7 +244,7 @@ var result = await _templateRenderer.RenderAsync(
You will see the localized result: You will see the localized result:
````csharp ````csharp
<a href="https://abp.io/example-link?userId=123&token=ABC">Click here to reset your password</a> <a title="Reset my password" href="https://abp.io/example-link?userId=123&token=ABC">Hi john, 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. > If you define the [default localization resource](Localization.md) for your application, then no need to declare the resource type for the template definition.

4
docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md

@ -88,6 +88,8 @@ You can set some of the attributes on your c# property, or directly on html tag.
* `label`: Sets the label for input. * `label`: Sets the label for input.
* `display-required-symbol`: Adds the required symbol (*) to label if input is required. Default `True`. * `display-required-symbol`: Adds the required symbol (*) to label if input is required. Default `True`.
`asp-format`, `name` and `value` attributes of [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-input-tag-helper) are also valid for `abp-input` tag helper.
### Label & Localization ### Label & Localization
You can set label of your input in different ways: You can set label of your input in different ways:
@ -258,4 +260,4 @@ You can set some of the attributes on your c# property, or directly on html tag.
#### Tag Attributes #### Tag Attributes
- `asp-items`: Sets the select data. This Should be a list of SelectListItem. - `asp-items`: Sets the select data. This Should be a list of SelectListItem.
- `Inline`: If true, radio buttons will be in single line, next to each other. If false, they will be under each other. - `Inline`: If true, radio buttons will be in single line, next to each other. If false, they will be under each other.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
@ -12,6 +13,7 @@ using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.DynamicProxy; using Volo.Abp.DynamicProxy;
using Volo.Abp.Localization;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
@ -21,12 +23,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
private readonly IHtmlGenerator _generator; private readonly IHtmlGenerator _generator;
private readonly HtmlEncoder _encoder; private readonly HtmlEncoder _encoder;
private readonly IAbpTagHelperLocalizer _tagHelperLocalizer; private readonly IAbpTagHelperLocalizer _tagHelperLocalizer;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
public AbpSelectTagHelperService(IHtmlGenerator generator, HtmlEncoder encoder, IAbpTagHelperLocalizer tagHelperLocalizer) public AbpSelectTagHelperService(
IHtmlGenerator generator,
HtmlEncoder encoder,
IAbpTagHelperLocalizer tagHelperLocalizer,
IStringLocalizerFactory stringLocalizerFactory)
{ {
_generator = generator; _generator = generator;
_encoder = encoder; _encoder = encoder;
_tagHelperLocalizer = tagHelperLocalizer; _tagHelperLocalizer = tagHelperLocalizer;
_stringLocalizerFactory = stringLocalizerFactory;
} }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -209,74 +217,33 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
} }
var containerLocalizer = _tagHelperLocalizer.GetLocalizerOrNull(explorer.Container.ModelType.Assembly); var containerLocalizer = _tagHelperLocalizer.GetLocalizerOrNull(explorer.Container.ModelType.Assembly);
IStringLocalizer modelObjectLocalizer = null;
if (explorer.Model != null)
{
modelObjectLocalizer = _tagHelperLocalizer.GetLocalizerOrNull(ProxyHelper.UnProxy(explorer.Model).GetType().Assembly);
}
selectItems.AddRange(enumType.GetEnumNames()
.Select(enumName => new SelectListItem
{
Value = Convert.ToUInt64(Enum.Parse(enumType, enumName)).ToString(),
Text = GetLocalizedEnumFieldName(containerLocalizer, modelObjectLocalizer, enumType, enumName)
}));
return selectItems;
}
protected virtual string GetLocalizedEnumFieldName(
IStringLocalizer containerLocalizer,
IStringLocalizer modelObjectLocalizer,
Type enumType,
string fieldName)
{
LocalizedString localizedString;
//Look for the enum name + enum field name
var localizationKey = enumType.Name + "." + fieldName;
if (containerLocalizer != null)
{
localizedString = containerLocalizer[localizationKey];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
}
if (modelObjectLocalizer != null) foreach (var enumValue in enumType.GetEnumValues())
{ {
localizedString = modelObjectLocalizer[localizationKey]; var memberName = enumType.GetEnumName(enumValue);
if (!localizedString.ResourceNotFound) var localizedMemberName = AbpInternalLocalizationHelper.LocalizeWithFallback(
new[]
{
containerLocalizer,
_stringLocalizerFactory.CreateDefaultOrNull()
},
new[]
{
$"Enum:{enumType.Name}.{memberName}",
$"{enumType.Name}.{memberName}",
memberName
},
memberName
);
selectItems.Add(new SelectListItem
{ {
return localizedString.Value; Value = enumValue.ToString(),
} Text = localizedMemberName
} });
//Look for the enum field name
localizationKey = fieldName;
if (containerLocalizer != null)
{
localizedString = containerLocalizer[localizationKey];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
}
if (modelObjectLocalizer != null)
{
localizedString = modelObjectLocalizer[localizationKey];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
} }
return fieldName; return selectItems;
} }
protected virtual List<SelectListItem> GetSelectItemsFromAttribute( protected virtual List<SelectListItem> GetSelectItemsFromAttribute(

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

11
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ActionResultHelper.cs

@ -8,7 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc
{ {
public static class ActionResultHelper public static class ActionResultHelper
{ {
public static List<Type> ObjectResultTypes { get; } public static List<Type> ObjectResultTypes { get; }
static ActionResultHelper() static ActionResultHelper()
{ {
@ -20,10 +20,15 @@ namespace Volo.Abp.AspNetCore.Mvc
}; };
} }
public static bool IsObjectResult(Type returnType) public static bool IsObjectResult(Type returnType, params Type[] excludeTypes)
{ {
returnType = AsyncHelper.UnwrapTask(returnType); returnType = AsyncHelper.UnwrapTask(returnType);
if (!excludeTypes.IsNullOrEmpty() && excludeTypes.Any(t => t.IsAssignableFrom(returnType)))
{
return false;
}
if (!typeof(IActionResult).IsAssignableFrom(returnType)) if (!typeof(IActionResult).IsAssignableFrom(returnType))
{ {
return true; return true;
@ -32,4 +37,4 @@ namespace Volo.Abp.AspNetCore.Mvc
return ObjectResultTypes.Any(t => t.IsAssignableFrom(returnType)); return ObjectResultTypes.Any(t => t.IsAssignableFrom(returnType));
} }
} }
} }

69
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs

@ -39,7 +39,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
{ {
var objectExtensionsDto = new ObjectExtensionsDto var objectExtensionsDto = new ObjectExtensionsDto
{ {
Modules = new Dictionary<string, ModuleExtensionDto>() Modules = new Dictionary<string, ModuleExtensionDto>(),
Enums = new Dictionary<string, ExtensionEnumDto>()
}; };
foreach (var moduleConfig in ObjectExtensionManager.Instance.Modules()) foreach (var moduleConfig in ObjectExtensionManager.Instance.Modules())
@ -47,6 +48,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
objectExtensionsDto.Modules[moduleConfig.Key] = CreateModuleExtensionDto(moduleConfig.Value); objectExtensionsDto.Modules[moduleConfig.Key] = CreateModuleExtensionDto(moduleConfig.Value);
} }
FillEnums(objectExtensionsDto);
return objectExtensionsDto; return objectExtensionsDto;
} }
@ -104,9 +107,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
var extensionPropertyDto = new ExtensionPropertyDto var extensionPropertyDto = new ExtensionPropertyDto
{ {
Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyConfig.Type), Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyConfig.Type),
TypeSimple = propertyConfig.Type.IsEnum TypeSimple = GetSimpleTypeName(propertyConfig),
? "enum"
: TypeHelper.GetSimplifiedName(propertyConfig.Type),
Attributes = new List<ExtensionPropertyAttributeDto>(), Attributes = new List<ExtensionPropertyAttributeDto>(),
DisplayName = CreateDisplayNameDto(propertyConfig), DisplayName = CreateDisplayNameDto(propertyConfig),
Configuration = new Dictionary<string, object>(), Configuration = new Dictionary<string, object>(),
@ -158,6 +159,26 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
return extensionPropertyDto; return extensionPropertyDto;
} }
protected virtual string GetSimpleTypeName(ExtensionPropertyConfiguration propertyConfig)
{
if (propertyConfig.Type.IsEnum)
{
return "enum";
}
if (propertyConfig.IsDate())
{
return "date";
}
if (propertyConfig.IsDateTime())
{
return "datetime";
}
return TypeHelper.GetSimplifiedName(propertyConfig.Type);
}
protected virtual LocalizableStringDto CreateDisplayNameDto(ExtensionPropertyConfiguration propertyConfig) protected virtual LocalizableStringDto CreateDisplayNameDto(ExtensionPropertyConfiguration propertyConfig)
{ {
if (propertyConfig.DisplayName == null) if (propertyConfig.DisplayName == null)
@ -183,5 +204,45 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
return null; return null;
} }
protected virtual void FillEnums(ObjectExtensionsDto objectExtensionsDto)
{
var enumProperties = ObjectExtensionManager.Instance.Modules().Values
.SelectMany(
m => m.Entities.Values.SelectMany(
e => e.GetProperties()
)
)
.Where(p => p.Type.IsEnum)
.ToList();
foreach (var enumProperty in enumProperties)
{
// ReSharper disable once AssignNullToNotNullAttribute (enumProperty.Type.FullName can not be null for this case)
objectExtensionsDto.Enums[enumProperty.Type.FullName] = CreateExtensionEnumDto(enumProperty);
}
}
protected virtual ExtensionEnumDto CreateExtensionEnumDto(ExtensionPropertyConfiguration enumProperty)
{
var extensionEnumDto = new ExtensionEnumDto
{
Fields = new List<ExtensionEnumFieldDto>(),
LocalizationResource = enumProperty.GetLocalizationResourceNameOrNull()
};
foreach (var enumValue in enumProperty.Type.GetEnumValues())
{
extensionEnumDto.Fields.Add(
new ExtensionEnumFieldDto
{
Name = enumProperty.Type.GetEnumName(enumValue),
Value = enumValue
}
);
}
return extensionEnumDto;
}
} }
} }

18
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs

@ -1,4 +1,4 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -25,7 +25,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
public AbpExceptionPageFilter( public AbpExceptionPageFilter(
IExceptionToErrorInfoConverter errorInfoConverter, IExceptionToErrorInfoConverter errorInfoConverter,
IHttpExceptionStatusCodeFinder statusCodeFinder, IHttpExceptionStatusCodeFinder statusCodeFinder,
IJsonSerializer jsonSerializer) IJsonSerializer jsonSerializer)
{ {
_errorInfoConverter = errorInfoConverter; _errorInfoConverter = errorInfoConverter;
@ -34,13 +34,12 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
Logger = NullLogger<AbpExceptionPageFilter>.Instance; Logger = NullLogger<AbpExceptionPageFilter>.Instance;
} }
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{ {
if (context.HandlerMethod == null || !ShouldHandleException(context)) if (context.HandlerMethod == null || !ShouldHandleException(context))
@ -54,20 +53,20 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{ {
return;; return;;
} }
await HandleAndWrapException(pageHandlerExecutedContext); await HandleAndWrapException(pageHandlerExecutedContext);
} }
protected virtual bool ShouldHandleException(PageHandlerExecutingContext context) protected virtual bool ShouldHandleException(PageHandlerExecutingContext context)
{ {
//TODO: Create DontWrap attribute to control wrapping..? //TODO: Create DontWrap attribute to control wrapping..?
if (context.ActionDescriptor.IsPageAction() && if (context.ActionDescriptor.IsPageAction() &&
ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType)) ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType, typeof(void)))
{ {
return true; return true;
} }
if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json)) if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json))
{ {
return true; return true;
@ -107,6 +106,5 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
context.Exception = null; //Handled! context.Exception = null; //Handled!
} }
} }
} }

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

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

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

@ -1,5 +1,4 @@
using System; using System;
using System.ComponentModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending;
@ -61,7 +60,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
return value; return value;
} }
return TypeHelper.ConvertFromString(propertyInfo.Type, value); return TypeHelper.ConvertFromString(
propertyInfo.Type,
value
);
} }
} }
} }

6
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs

@ -38,7 +38,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
context.HttpContext.Items["_AbpActionInfo"] = new AbpActionInfoInHttpContext context.HttpContext.Items["_AbpActionInfo"] = new AbpActionInfoInHttpContext
{ {
IsObjectResult = ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType) IsObjectResult = ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType, typeof(void))
}; };
if (unitOfWorkAttr?.IsDisabled == true) if (unitOfWorkAttr?.IsDisabled == true)
@ -71,7 +71,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
} }
} }
} }
private AbpUnitOfWorkOptions CreateOptions(PageHandlerExecutingContext context, UnitOfWorkAttribute unitOfWorkAttribute) private AbpUnitOfWorkOptions CreateOptions(PageHandlerExecutingContext context, UnitOfWorkAttribute unitOfWorkAttribute)
{ {
var options = new AbpUnitOfWorkOptions(); var options = new AbpUnitOfWorkOptions();
@ -102,4 +102,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
return result.Exception == null || result.ExceptionHandled; return result.Exception == null || result.ExceptionHandled;
} }
} }
} }

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

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

17
framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpSignalRUserIdProvider.cs

@ -1,21 +1,28 @@
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users; using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.SignalR namespace Volo.Abp.AspNetCore.SignalR
{ {
public class AbpSignalRUserIdProvider : IUserIdProvider, ITransientDependency public class AbpSignalRUserIdProvider : IUserIdProvider, ITransientDependency
{ {
public ICurrentUser CurrentUser { get; } private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public AbpSignalRUserIdProvider(ICurrentUser currentUser) private readonly ICurrentUser _currentUser;
public AbpSignalRUserIdProvider(ICurrentPrincipalAccessor currentPrincipalAccessor, ICurrentUser currentUser)
{ {
CurrentUser = currentUser; _currentPrincipalAccessor = currentPrincipalAccessor;
_currentUser = currentUser;
} }
public virtual string GetUserId(HubConnectionContext connection) public virtual string GetUserId(HubConnectionContext connection)
{ {
return CurrentUser.Id?.ToString(); using (_currentPrincipalAccessor.Change(connection.User))
{
return _currentUser.Id?.ToString();
}
} }
} }
} }

7
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/HttpContextCurrentPrincipalAccessor.cs

@ -6,13 +6,16 @@ namespace Volo.Abp.AspNetCore.Security.Claims
{ {
public class HttpContextCurrentPrincipalAccessor : ThreadCurrentPrincipalAccessor public class HttpContextCurrentPrincipalAccessor : ThreadCurrentPrincipalAccessor
{ {
public override ClaimsPrincipal Principal => _httpContextAccessor.HttpContext?.User ?? base.Principal;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextCurrentPrincipalAccessor(IHttpContextAccessor httpContextAccessor) public HttpContextCurrentPrincipalAccessor(IHttpContextAccessor httpContextAccessor)
{ {
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
} }
public override ClaimsPrincipal GetClaimsPrincipal()
{
return _httpContextAccessor.HttpContext?.User ?? base.GetClaimsPrincipal();
}
} }
} }

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.ProjectBuilding; using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.ProjectBuilding.Building; using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Templates.Console;
using Volo.Abp.Cli.Utils; using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -266,6 +267,7 @@ namespace Volo.Abp.Cli.Commands
protected virtual MobileApp GetMobilePreference(CommandLineArgs commandLineArgs) protected virtual MobileApp GetMobilePreference(CommandLineArgs commandLineArgs)
{ {
var optionValue = commandLineArgs.Options.GetOrNull(Options.Mobile.Short, Options.Mobile.Long); var optionValue = commandLineArgs.Options.GetOrNull(Options.Mobile.Short, Options.Mobile.Long);
var template = commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long);
switch (optionValue) switch (optionValue)
{ {
case "none": case "none":
@ -273,7 +275,7 @@ namespace Volo.Abp.Cli.Commands
case "react-native": case "react-native":
return MobileApp.ReactNative; return MobileApp.ReactNative;
default: default:
return MobileApp.ReactNative; return ConsoleTemplate.TemplateName == template ? MobileApp.None : MobileApp.ReactNative;
} }
} }

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs

@ -12,6 +12,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Cli.Http; using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.ProjectBuilding.Templates.App; using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectBuilding.Templates.Console;
using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Http; using Volo.Abp.Http;
@ -63,12 +64,12 @@ namespace Volo.Abp.Cli.ProjectBuilding
Logger.LogWarning("The remote service is currently unavailable, please specify the version."); Logger.LogWarning("The remote service is currently unavailable, please specify the version.");
Logger.LogWarning(string.Empty); Logger.LogWarning(string.Empty);
Logger.LogWarning("Find the following template in your cache directory: "); Logger.LogWarning("Find the following template in your cache directory: ");
Logger.LogWarning("\t Template Name\tVersion"); Logger.LogWarning("\tTemplate Name\tVersion");
var templateList = GetLocalTemplates(); var templateList = GetLocalTemplates();
foreach (var cacheFile in templateList) foreach (var cacheFile in templateList)
{ {
Logger.LogWarning($"\t {cacheFile.TemplateName}\t\t{cacheFile.Version}"); Logger.LogWarning($"\t{cacheFile.TemplateName}\t\t{cacheFile.Version}");
} }
Logger.LogWarning(string.Empty); Logger.LogWarning(string.Empty);
@ -241,7 +242,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
stringBuilder.AppendLine(cacheFile); stringBuilder.AppendLine(cacheFile);
} }
var matches = Regex.Matches(stringBuilder.ToString(), $"({AppTemplate.TemplateName}|{AppProTemplate.TemplateName}|{ModuleTemplate.TemplateName}|{ModuleProTemplate.TemplateName})-(.+).zip"); var matches = Regex.Matches(stringBuilder.ToString(), $"({AppTemplate.TemplateName}|{AppProTemplate.TemplateName}|{ModuleTemplate.TemplateName}|{ModuleProTemplate.TemplateName}|{ConsoleTemplate.TemplateName})-(.+).zip");
foreach (Match match in matches) foreach (Match match in matches)
{ {
templateList.Add((match.Groups[1].Value, match.Groups[2].Value)); templateList.Add((match.Groups[1].Value, match.Groups[2].Value));
@ -278,4 +279,4 @@ namespace Volo.Abp.Cli.ProjectBuilding
public string Version { get; set; } public string Version { get; set; }
} }
} }
} }

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs

@ -1,6 +1,7 @@
using System; using System;
using Volo.Abp.Cli.ProjectBuilding.Building; using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Templates.App; using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectBuilding.Templates.Console;
using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -25,9 +26,11 @@ namespace Volo.Abp.Cli.ProjectBuilding
return new ModuleTemplate(); return new ModuleTemplate();
case ModuleProTemplate.TemplateName: case ModuleProTemplate.TemplateName:
return new ModuleProTemplate(); return new ModuleProTemplate();
case ConsoleTemplate.TemplateName:
return new ConsoleTemplate();
default: default:
throw new Exception("There is no template found with given name: " + name); throw new Exception("There is no template found with given name: " + name);
} }
} }
} }
} }

16
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Console/ConsoleTemplate.cs

@ -0,0 +1,16 @@
namespace Volo.Abp.Cli.ProjectBuilding.Templates.Console
{
public class ConsoleTemplate : ConsoleTemplateBase
{
/// <summary>
/// "console".
/// </summary>
public const string TemplateName = "console";
public ConsoleTemplate()
: base(TemplateName)
{
DocumentUrl = CliConsts.DocsLink + "/en/abp/latest/Getting-Started-Console-Application";
}
}
}

13
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Console/ConsoleTemplateBase.cs

@ -0,0 +1,13 @@
using JetBrains.Annotations;
using Volo.Abp.Cli.ProjectBuilding.Building;
namespace Volo.Abp.Cli.ProjectBuilding.Templates.Console
{
public abstract class ConsoleTemplateBase : TemplateInfo
{
protected ConsoleTemplateBase([NotNull] string name) :
base(name)
{
}
}
}

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

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

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

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

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

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

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

@ -2,14 +2,23 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations; using JetBrains.Annotations;
using Volo.Abp.Localization;
namespace Volo.Abp.Reflection namespace Volo.Abp.Reflection
{ {
public static class TypeHelper public static class TypeHelper
{ {
private static readonly HashSet<Type> FloatingTypes = new HashSet<Type>
{
typeof(float),
typeof(double),
typeof(decimal)
};
private static readonly HashSet<Type> NonNullablePrimitiveTypes = new HashSet<Type> private static readonly HashSet<Type> NonNullablePrimitiveTypes = new HashSet<Type>
{ {
typeof(byte), typeof(byte),
@ -40,7 +49,7 @@ namespace Volo.Abp.Reflection
{ {
return false; return false;
} }
var type = obj.GetType(); var type = obj.GetType();
if (!type.GetTypeInfo().IsGenericType) if (!type.GetTypeInfo().IsGenericType)
{ {
@ -297,9 +306,39 @@ namespace Volo.Abp.Reflection
public static object ConvertFromString(Type targetType, string value) public static object ConvertFromString(Type targetType, string value)
{ {
return TypeDescriptor if (value == null)
.GetConverter(targetType) {
.ConvertFromString(value); return null;
}
var converter = TypeDescriptor.GetConverter(targetType);
if (IsFloatingType(targetType))
{
using (CultureHelper.Use(CultureInfo.InvariantCulture))
{
return converter.ConvertFromString(value.Replace(',', '.'));
}
}
return converter.ConvertFromString(value);
}
public static bool IsFloatingType(Type type, bool includeNullable = true)
{
if (FloatingTypes.Contains(type))
{
return true;
}
if (includeNullable &&
IsNullable(type) &&
FloatingTypes.Contains(type.GenericTypeArguments[0]))
{
return true;
}
return false;
} }
public static object ConvertFrom<TTargetType>(object value) public static object ConvertFrom<TTargetType>(object value)
@ -313,5 +352,12 @@ namespace Volo.Abp.Reflection
.GetConverter(targetType) .GetConverter(targetType)
.ConvertFrom(value); .ConvertFrom(value);
} }
public static Type StripNullable(Type type)
{
return IsNullable(type)
? type.GenericTypeArguments[0]
: type;
}
} }
} }

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

@ -58,12 +58,14 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters
return dictionary; return dictionary;
} }
private static object GetNormalizedValue(Dictionary<string, object> dictionary, ObjectExtensionPropertyInfo property) private static object GetNormalizedValue(
Dictionary<string, object> dictionary,
ObjectExtensionPropertyInfo property)
{ {
var value = dictionary.GetOrDefault(property.Name); var value = dictionary.GetOrDefault(property.Name);
if (value == null) if (value == null)
{ {
return null; return property.GetDefaultValue();
} }
try try

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

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

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

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

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

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

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

@ -6,7 +6,7 @@ using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending.Modularity namespace Volo.Abp.ObjectExtending.Modularity
{ {
public class ExtensionPropertyConfiguration : IHasNameWithLocalizableDisplayName public class ExtensionPropertyConfiguration : IHasNameWithLocalizableDisplayName, IBasicObjectExtensionPropertyInfo
{ {
[NotNull] [NotNull]
public EntityExtensionConfiguration EntityExtensionConfiguration { get; } public EntityExtensionConfiguration EntityExtensionConfiguration { get; }

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

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

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

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace Volo.Abp.ObjectExtending.Modularity namespace Volo.Abp.ObjectExtending.Modularity

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

@ -7,7 +7,7 @@ using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending namespace Volo.Abp.ObjectExtending
{ {
public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName, IBasicObjectExtensionPropertyInfo
{ {
[NotNull] [NotNull]
public ObjectExtensionInfo ObjectExtension { get; } public ObjectExtensionInfo ObjectExtension { get; }

5
framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ICurrentPrincipalAccessor.cs

@ -1,9 +1,12 @@
using System.Security.Claims; using System;
using System.Security.Claims;
namespace Volo.Abp.Security.Claims namespace Volo.Abp.Security.Claims
{ {
public interface ICurrentPrincipalAccessor public interface ICurrentPrincipalAccessor
{ {
ClaimsPrincipal Principal { get; } ClaimsPrincipal Principal { get; }
IDisposable Change(ClaimsPrincipal principal);
} }
} }

29
framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ThreadCurrentPrincipalAccessor.cs

@ -1,4 +1,5 @@
using System.Security.Claims; using System;
using System.Security.Claims;
using System.Threading; using System.Threading;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -6,6 +7,28 @@ namespace Volo.Abp.Security.Claims
{ {
public class ThreadCurrentPrincipalAccessor : ICurrentPrincipalAccessor, ISingletonDependency public class ThreadCurrentPrincipalAccessor : ICurrentPrincipalAccessor, ISingletonDependency
{ {
public virtual ClaimsPrincipal Principal => Thread.CurrentPrincipal as ClaimsPrincipal; public ClaimsPrincipal Principal => _currentPrincipal.Value ?? GetClaimsPrincipal();
private readonly AsyncLocal<ClaimsPrincipal> _currentPrincipal = new AsyncLocal<ClaimsPrincipal>();
public virtual ClaimsPrincipal GetClaimsPrincipal()
{
return Thread.CurrentPrincipal as ClaimsPrincipal;
}
public virtual IDisposable Change(ClaimsPrincipal principal)
{
return SetCurrent(principal);
}
private IDisposable SetCurrent(ClaimsPrincipal principal)
{
var parent = Principal;
_currentPrincipal.Value = principal;
return new DisposeAction(() =>
{
_currentPrincipal.Value = parent;
});
}
} }
} }

50
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateLocalizer.cs

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Scriban;
using Scriban.Runtime;
using Scriban.Syntax;
namespace Volo.Abp.TextTemplating
{
public class TemplateLocalizer : IScriptCustomFunction
{
private readonly IStringLocalizer _localizer;
public TemplateLocalizer(IStringLocalizer localizer)
{
_localizer = localizer;
}
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments,
ScriptBlockStatement blockStatement)
{
return GetString(arguments);
}
public ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments,
ScriptBlockStatement blockStatement)
{
return new ValueTask<object>(GetString(arguments));
}
private string GetString(ScriptArray arguments)
{
if (arguments.IsNullOrEmpty())
{
return string.Empty;
}
var name = arguments[0];
if (name == null || name.ToString().IsNullOrWhiteSpace())
{
return string.Empty;
}
var args = arguments.Skip(1).Where(x => x != null && !x.ToString().IsNullOrWhiteSpace()).ToArray();
return args.Any() ? _localizer[name.ToString(), args] : _localizer[name.ToString()];
}
}
}

9
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs

@ -140,12 +140,7 @@ namespace Volo.Abp.TextTemplating
var localizer = GetLocalizerOrNull(templateDefinition); var localizer = GetLocalizerOrNull(templateDefinition);
if (localizer != null) if (localizer != null)
{ {
scriptObject.Import( scriptObject.SetValue("L", new TemplateLocalizer(localizer), true);
"L",
new Func<string, string>(
name => localizer[name]
)
);
} }
context.PushGlobal(scriptObject); context.PushGlobal(scriptObject);
@ -163,4 +158,4 @@ namespace Volo.Abp.TextTemplating
return _stringLocalizerFactory.CreateDefaultOrNull(); return _stringLocalizerFactory.CreateDefaultOrNull();
} }
} }
} }

4
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json

@ -12,7 +12,7 @@
"The field {0} must be between {1} and {2}.": "字段{0}值必须在{1}和{2}范围内.", "The field {0} must be between {1} and {2}.": "字段{0}值必须在{1}和{2}范围内.",
"The field {0} must match the regular expression '{1}'.": "字段{0}与请求的格式不匹配。", "The field {0} must match the regular expression '{1}'.": "字段{0}与请求的格式不匹配。",
"The {0} field is required.": "字段{0}不可为空.", "The {0} field is required.": "字段{0}不可为空.",
"The field {0} must be a string with a maximum length of {1}.": "字段{0}必须是长度为{1}的字符串.", "The field {0} must be a string with a maximum length of {1}.": "字段{0}必须是最大长度为{1}的字符串.",
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "字段{0}必须是最小长度为{2}并且最大长度{1}的字符串.", "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "字段{0}必须是最小长度为{2}并且最大长度{1}的字符串.",
"The {0} field is not a valid fully-qualified http, https, or ftp URL.": "字段{0}不是有效的完全限定的http,https或ftp URL.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "字段{0}不是有效的完全限定的http,https或ftp URL.",
"The field {0} is invalid.": "字段{0}是无效值.", "The field {0} is invalid.": "字段{0}是无效值.",
@ -31,4 +31,4 @@
"ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "字段{0}不是有效的完全限定的http,https或ftp URL.", "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "字段{0}不是有效的完全限定的http,https或ftp URL.",
"ThisFieldIsInvalid.": "该字段无效." "ThisFieldIsInvalid.": "该字段无效."
} }
} }

2
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/FakeAuthenticationMiddleware.cs

@ -29,4 +29,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Authorization
await next(context); await next(context);
} }
} }
} }

27
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs

@ -1,18 +1,35 @@
using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{ {
public class ExceptionTestPage : AbpPageModel public class ExceptionTestPage : AbpPageModel
{ {
public void OnGetUserFriendlyException1() public void OnGetUserFriendlyException_void()
{ {
throw new UserFriendlyException("This is a sample exception!"); throw new UserFriendlyException("This is a sample exception!");
} }
public IActionResult OnGetUserFriendlyException2() public Task OnGetUserFriendlyException_Task()
{
throw new UserFriendlyException("This is a sample exception!");
}
public IActionResult OnGetUserFriendlyException_ActionResult()
{
throw new UserFriendlyException("This is a sample exception!");
}
public JsonResult OnGetUserFriendlyException_JsonResult()
{ {
throw new UserFriendlyException("This is a sample exception!"); throw new UserFriendlyException("This is a sample exception!");
} }
public Task<JsonResult> OnGetUserFriendlyException_Task_JsonResult()
{
throw new UserFriendlyException("This is a sample exception!");
}
} }
} }

64
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs

@ -1,4 +1,4 @@
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@ -24,15 +24,33 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
} }
[Fact] [Fact]
public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Void_Return_Value() public async Task Should_Not_Handle_Exceptions_For_Void_Return_Values()
{ {
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException1", HttpStatusCode.Forbidden); await Assert.ThrowsAsync<UserFriendlyException>(
result.Error.ShouldNotBeNull(); async () => await GetResponseAsStringAsync(
result.Error.Message.ShouldBe("This is a sample exception!"); "/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException_Void"
)
);
#pragma warning disable 4014 #pragma warning disable 4014
_fakeExceptionSubscriber _fakeExceptionSubscriber
.Received() .DidNotReceive()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
[Fact]
public async Task Should_Not_Handle_Exceptions_For_Task_Return_Values()
{
await Assert.ThrowsAsync<UserFriendlyException>(
async () => await GetResponseAsStringAsync(
"/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException_Task"
)
);
#pragma warning disable 4014
_fakeExceptionSubscriber
.DidNotReceive()
.HandleAsync(Arg.Any<ExceptionNotificationContext>()); .HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014 #pragma warning restore 4014
} }
@ -41,8 +59,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
public async Task Should_Not_Handle_Exceptions_For_ActionResult_Return_Values() public async Task Should_Not_Handle_Exceptions_For_ActionResult_Return_Values()
{ {
await Assert.ThrowsAsync<UserFriendlyException>( await Assert.ThrowsAsync<UserFriendlyException>(
async () => await GetResponseAsObjectAsync<RemoteServiceErrorResponse>( async () => await GetResponseAsStringAsync(
"/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException2" "/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException_ActionResult"
) )
); );
@ -50,7 +68,35 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
_fakeExceptionSubscriber _fakeExceptionSubscriber
.DidNotReceive() .DidNotReceive()
.HandleAsync(Arg.Any<ExceptionNotificationContext>()); .HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
[Fact]
public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Object_Return_Value()
{
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException_JsonResult", HttpStatusCode.Forbidden);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("This is a sample exception!");
#pragma warning disable 4014
_fakeExceptionSubscriber
.Received()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
[Fact]
public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Task_Object_Return_Value()
{
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException_Task_JsonResult", HttpStatusCode.Forbidden);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("This is a sample exception!");
#pragma warning disable 4014
_fakeExceptionSubscriber
.Received()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014 #pragma warning restore 4014
} }
} }
} }

6
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs

@ -14,9 +14,9 @@ namespace Volo.Abp.AspNetCore.Mvc.Features
} }
[RequiresFeature("NotAllowedFeature")] [RequiresFeature("NotAllowedFeature")]
public void OnGetNotAllowedFeature() public ObjectResult OnGetNotAllowedFeature()
{ {
return new ObjectResult(42);
} }
public ObjectResult OnGetNoFeature() public ObjectResult OnGetNoFeature()
@ -24,4 +24,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Features
return new ObjectResult(42); return new ObjectResult(42);
} }
} }
} }

8
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs

@ -32,23 +32,23 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
} }
[UnitOfWork(isTransactional: true)] [UnitOfWork(isTransactional: true)]
public void OnGetHandledException() public ObjectResult OnGetHandledException()
{ {
CurrentUnitOfWork.ShouldNotBeNull(); CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue(); CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue();
throw new UserFriendlyException("This is a sample exception!"); throw new UserFriendlyException("This is a sample exception!");
} }
public ObjectResult OnGetExceptionOnComplete() public ObjectResult OnGetExceptionOnComplete()
{ {
CurrentUnitOfWork.ShouldNotBeNull(); CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse(); CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse();
_testUnitOfWorkConfig.ThrowExceptionOnComplete = true; _testUnitOfWorkConfig.ThrowExceptionOnComplete = true;
//Prevent rendering of pages. //Prevent rendering of pages.
return new ObjectResult(""); return new ObjectResult("");
} }
} }
} }

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

@ -80,6 +80,7 @@ namespace Volo.Abp.Reflection
TypeHelper.GetDefaultValue(typeof(byte)).ShouldBe(0); TypeHelper.GetDefaultValue(typeof(byte)).ShouldBe(0);
TypeHelper.GetDefaultValue(typeof(int)).ShouldBe(0); TypeHelper.GetDefaultValue(typeof(int)).ShouldBe(0);
TypeHelper.GetDefaultValue(typeof(string)).ShouldBeNull(); TypeHelper.GetDefaultValue(typeof(string)).ShouldBeNull();
TypeHelper.GetDefaultValue(typeof(MyEnum)).ShouldBe(MyEnum.EnumValue0);
} }
[Fact] [Fact]
@ -94,5 +95,11 @@ namespace Volo.Abp.Reflection
{ {
} }
public enum MyEnum
{
EnumValue0,
EnumValue1,
}
} }
} }

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

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

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

@ -138,23 +138,27 @@ namespace Volo.Abp.ObjectExtending
.AddOrUpdateProperty<MyExtensibleObject, string>("StringPropWithCustomDefaultValue", property => .AddOrUpdateProperty<MyExtensibleObject, string>("StringPropWithCustomDefaultValue", property =>
{ {
property.DefaultValue = "custom-value"; property.DefaultValue = "custom-value";
})
.AddOrUpdateProperty<MyExtensibleObject, MyTestEnum>("EnumProp", property =>
{
property.DefaultValue = MyTestEnum.EnumValue2;
}); });
_objectExtensionManager _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntProp") .GetPropertyOrNull<MyExtensibleObject>("IntProp")
.DefaultValue.ShouldBe(0); .GetDefaultValue().ShouldBe(0);
_objectExtensionManager _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntPropWithCustomDefaultValue") .GetPropertyOrNull<MyExtensibleObject>("IntPropWithCustomDefaultValue")
.DefaultValue.ShouldBe(42); .GetDefaultValue().ShouldBe(42);
_objectExtensionManager _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("BoolProp") .GetPropertyOrNull<MyExtensibleObject>("BoolProp")
.DefaultValue.ShouldBe(false); .GetDefaultValue().ShouldBe(false);
_objectExtensionManager _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntProp") .GetPropertyOrNull<MyExtensibleObject>("NullableIntProp")
.DefaultValue.ShouldBeNull(); .GetDefaultValue().ShouldBeNull();
var propWithDefaultValueFactory = _objectExtensionManager var propWithDefaultValueFactory = _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntPropWithCustomDefaultValueFactory"); .GetPropertyOrNull<MyExtensibleObject>("NullableIntPropWithCustomDefaultValueFactory");
@ -162,11 +166,15 @@ namespace Volo.Abp.ObjectExtending
_objectExtensionManager _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringProp") .GetPropertyOrNull<MyExtensibleObject>("StringProp")
.DefaultValue.ShouldBeNull(); .GetDefaultValue().ShouldBeNull();
_objectExtensionManager _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringPropWithCustomDefaultValue") .GetPropertyOrNull<MyExtensibleObject>("StringPropWithCustomDefaultValue")
.DefaultValue.ShouldBe("custom-value"); .GetDefaultValue().ShouldBe("custom-value");
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("EnumProp")
.GetDefaultValue().ShouldBe(MyTestEnum.EnumValue2);
} }
private class MyExtensibleObject : ExtensibleObject private class MyExtensibleObject : ExtensibleObject

50
framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/CurrentPrincipalAccessor_Test.cs

@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Security.Claims;
using Shouldly;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Security.Claims
{
public class CurrentPrincipalAccessor_Test : AbpIntegratedTest<AbpSecurityTestModule>
{
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public CurrentPrincipalAccessor_Test()
{
_currentPrincipalAccessor = GetRequiredService<ICurrentPrincipalAccessor>();
}
[Fact]
public void Should_Get_Changed_Principal_If()
{
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Name,"bob"),
new Claim(ClaimTypes.NameIdentifier,"123456")
}));
var claimsPrincipal2 = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Name,"lee"),
new Claim(ClaimTypes.NameIdentifier,"654321")
}));
_currentPrincipalAccessor.Principal.ShouldBe(null);
using (_currentPrincipalAccessor.Change(claimsPrincipal))
{
_currentPrincipalAccessor.Principal.ShouldBe(claimsPrincipal);
using (_currentPrincipalAccessor.Change(claimsPrincipal2))
{
_currentPrincipalAccessor.Principal.ShouldBe(claimsPrincipal2);
}
_currentPrincipalAccessor.Principal.ShouldBe(claimsPrincipal);
}
_currentPrincipalAccessor.Principal.ShouldBeNull();
}
}
}

7
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json

@ -1,6 +1,7 @@
{ {
"culture": "en", "culture": "en",
"texts": { "texts": {
"HelloText": "Hello" "HelloText": "Hello {0}",
} "HowAreYou": "how are you?"
} }
}

7
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json

@ -1,6 +1,7 @@
{ {
"culture": "tr", "culture": "tr",
"texts": { "texts": {
"HelloText": "Merhaba" "HelloText": "Merhaba {0}",
} "HowAreYou": "nasılsın?"
} }
}

2
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl

@ -1 +1 @@
{{L "HelloText"}}. Please click to the following link to get an email to reset your password! {{L "HelloText" model.name}}, {{L "HowAreYou" }}. Please click to the following link to get an email to reset your password!

21
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs

@ -81,13 +81,15 @@ namespace Volo.Abp.TextTemplating
{ {
(await _templateRenderer.RenderAsync( (await _templateRenderer.RenderAsync(
TestTemplates.ForgotPasswordEmail, TestTemplates.ForgotPasswordEmail,
new ForgotPasswordEmailModel("John"),
cultureName: "en" cultureName: "en"
)).ShouldBe("*BEGIN*Hello. Please click to the following link to get an email to reset your password!*END*"); )).ShouldBe("*BEGIN*Hello John, how are you?. Please click to the following link to get an email to reset your password!*END*");
(await _templateRenderer.RenderAsync( (await _templateRenderer.RenderAsync(
TestTemplates.ForgotPasswordEmail, TestTemplates.ForgotPasswordEmail,
model: new Dictionary<string, object>() { { "name", "John" } },
cultureName: "tr" cultureName: "tr"
)).ShouldBe("*BEGIN*Merhaba. Please click to the following link to get an email to reset your password!*END*"); )).ShouldBe("*BEGIN*Merhaba John, nasılsın?. Please click to the following link to get an email to reset your password!*END*");
} }
private class WelcomeEmailModel private class WelcomeEmailModel
@ -104,5 +106,20 @@ namespace Volo.Abp.TextTemplating
Name = name; Name = name;
} }
} }
private class ForgotPasswordEmailModel
{
public string Name { get; set; }
public ForgotPasswordEmailModel()
{
}
public ForgotPasswordEmailModel(string name)
{
Name = name;
}
}
} }
} }

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

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

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

@ -8,6 +8,7 @@
@using Volo.Blogging.Pages.Blog.Posts @using Volo.Blogging.Pages.Blog.Posts
@using Volo.Blogging.Areas.Blog.Helpers.TagHelpers @using Volo.Blogging.Areas.Blog.Helpers.TagHelpers
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs
@using Volo.Blogging.SocialMedia
@inject IAuthorizationService Authorization @inject IAuthorizationService Authorization
@inject IOptionsSnapshot<BloggingTwitterOptions> twitterOptions @inject IOptionsSnapshot<BloggingTwitterOptions> twitterOptions
@model DetailModel @model DetailModel
@ -21,6 +22,11 @@
ViewBag.TwitterDescription = Model.Post.Description; ViewBag.TwitterDescription = Model.Post.Description;
ViewBag.TwitterImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}"; ViewBag.TwitterImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}";
ViewBag.LinkedInUrl = Request.GetEncodedUrl();
ViewBag.LinkedInTitle = Model.Post.Title;
ViewBag.LinkedInDescription = Model.Post.Description;
ViewBag.LinkedInImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}";
var hasCommentingPermission = CurrentUser.IsAuthenticated; //TODO: Apply real policy! var hasCommentingPermission = CurrentUser.IsAuthenticated; //TODO: Apply real policy!
} }
@section scripts { @section scripts {
@ -129,7 +135,7 @@
<h5>@L["TagsInThisArticle"]</h5> <h5>@L["TagsInThisArticle"]</h5>
@foreach (var tag in Model.Post.Tags) @foreach (var tag in Model.Post.Tags)
{ {
<a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@tag.Name" class="tag">@tag.Name</a> <a asp-page="/Blogs/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@tag.Name" class="tag">@tag.Name</a>
} }
</div> </div>
} }

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

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

7
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/DocsAdminRemoteServiceConsts.cs

@ -0,0 +1,7 @@
namespace Volo.Docs.Admin
{
public static class DocsAdminRemoteServiceConsts
{
public const string RemoteServiceName = "AbpDocsAdmin";
}
}

2
modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.csproj

@ -12,6 +12,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Docs.Admin.Application.Contracts\Volo.Docs.Admin.Application.Contracts.csproj" /> <ProjectReference Include="..\Volo.Docs.Admin.Application.Contracts\Volo.Docs.Admin.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

12
modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo/Docs/Admin/DocsHttpApiClientModule.cs

@ -1,11 +1,17 @@
using Volo.Abp.Modularity; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Http.Client;
using Volo.Abp.Modularity;
namespace Volo.Docs.Admin namespace Volo.Docs.Admin
{ {
[DependsOn( [DependsOn(
typeof(DocsAdminApplicationContractsModule))] typeof(DocsAdminApplicationContractsModule),
typeof(AbpHttpClientModule))]
public class DocsAdminHttpApiClientModule : AbpModule public class DocsAdminHttpApiClientModule : AbpModule
{ {
//TODO: Create client proxies! public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddHttpClientProxies(typeof(DocsAdminApplicationContractsModule).Assembly, DocsAdminRemoteServiceConsts.RemoteServiceName);
}
} }
} }

7
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/DocsRemoteServiceConsts.cs

@ -0,0 +1,7 @@
namespace Volo.Docs
{
public static class DocsRemoteServiceConsts
{
public const string RemoteServiceName = "AbpDocs";
}
}

1
modules/docs/src/Volo.Docs.HttpApi.Client/Volo.Docs.HttpApi.Client.csproj

@ -12,6 +12,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Docs.Application.Contracts\Volo.Docs.Application.Contracts.csproj" /> <ProjectReference Include="..\Volo.Docs.Application.Contracts\Volo.Docs.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

13
modules/docs/src/Volo.Docs.HttpApi.Client/Volo/Docs/DocsHttpApiClientModule.cs

@ -1,11 +1,18 @@
using Volo.Abp.Modularity; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Http.Client;
using Volo.Abp.Modularity;
namespace Volo.Docs namespace Volo.Docs
{ {
[DependsOn( [DependsOn(
typeof(DocsApplicationContractsModule))] typeof(DocsApplicationContractsModule),
typeof(AbpHttpClientModule)
)]
public class DocsHttpApiClientModule : AbpModule public class DocsHttpApiClientModule : AbpModule
{ {
//TODO: Create client proxies public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddHttpClientProxies(typeof(DocsApplicationContractsModule).Assembly, DocsRemoteServiceConsts.RemoteServiceName);
}
} }
} }

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

@ -13,5 +13,7 @@ namespace Volo.Abp.Identity
Task<UserData> FindByUserNameAsync(string userName); Task<UserData> FindByUserNameAsync(string userName);
Task<ListResultDto<UserData>> SearchAsync(UserLookupSearchInputDto input); Task<ListResultDto<UserData>> SearchAsync(UserLookupSearchInputDto input);
Task<long> GetCountAsync(UserLookupCountInputDto input);
} }
} }

7
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UserLookupCountInputDto.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Identity
{
public class UserLookupCountInputDto
{
public string Filter { get; set; }
}
}

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

@ -2,10 +2,8 @@
namespace Volo.Abp.Identity namespace Volo.Abp.Identity
{ {
public class UserLookupSearchInputDto : LimitedResultRequestDto, ISortedResultRequest public class UserLookupSearchInputDto : PagedAndSortedResultRequestDto
{ {
public string Sorting { get; set; }
public string Filter { get; set; } public string Filter { get; set; }
} }
} }

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

@ -45,7 +45,8 @@ namespace Volo.Abp.Identity
var users = await UserLookupServiceProvider.SearchAsync( var users = await UserLookupServiceProvider.SearchAsync(
input.Sorting, input.Sorting,
input.Filter, input.Filter,
input.MaxResultCount input.MaxResultCount,
input.SkipCount
); );
return new ListResultDto<UserData>( return new ListResultDto<UserData>(
@ -54,5 +55,10 @@ namespace Volo.Abp.Identity
.ToList() .ToList()
); );
} }
public async Task<long> GetCountAsync(UserLookupCountInputDto input)
{
return await UserLookupServiceProvider.GetCountAsync(input.Filter);
}
} }
} }

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

@ -49,14 +49,16 @@ namespace Volo.Abp.Identity
} }
public virtual async Task<List<IUserData>> SearchAsync( public virtual async Task<List<IUserData>> SearchAsync(
string sorting, string sorting = null,
string filter, string filter = null,
int maxResultCount, int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var users = await UserRepository.GetListAsync( var users = await UserRepository.GetListAsync(
sorting: sorting, sorting: sorting,
maxResultCount: maxResultCount, maxResultCount: maxResultCount,
skipCount: skipCount,
filter: filter, filter: filter,
includeDetails: false, includeDetails: false,
cancellationToken: cancellationToken cancellationToken: cancellationToken
@ -64,5 +66,12 @@ namespace Volo.Abp.Identity
return users.Select(u => u.ToAbpUserData()).ToList(); return users.Select(u => u.ToAbpUserData()).ToList();
} }
public async Task<long> GetCountAsync(
string filter = null,
CancellationToken cancellationToken = new CancellationToken())
{
return await UserRepository.GetCountAsync(filter, cancellationToken);
}
} }
} }

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

@ -128,7 +128,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
u => u =>
u.UserName.Contains(filter) || u.UserName.Contains(filter) ||
u.Email.Contains(filter) || u.Email.Contains(filter) ||
(u.PhoneNumber != null && u.PhoneNumber.Contains(filter)) (u.Name != null && u.Name.Contains(filter)) ||
(u.Surname != null && u.Surname.Contains(filter))
) )
.OrderBy(sorting ?? nameof(IdentityUser.UserName)) .OrderBy(sorting ?? nameof(IdentityUser.UserName))
.PageBy(skipCount, maxResultCount) .PageBy(skipCount, maxResultCount)
@ -170,7 +171,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
!filter.IsNullOrWhiteSpace(), !filter.IsNullOrWhiteSpace(),
u => u =>
u.UserName.Contains(filter) || u.UserName.Contains(filter) ||
u.Email.Contains(filter) u.Email.Contains(filter) ||
(u.Name != null && u.Name.Contains(filter)) ||
(u.Surname != null && u.Surname.Contains(filter))
) )
.LongCountAsync(GetCancellationToken(cancellationToken)); .LongCountAsync(GetCancellationToken(cancellationToken));
} }

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

@ -29,20 +29,36 @@ namespace Volo.Abp.Identity
} }
public async Task<List<IUserData>> SearchAsync( public async Task<List<IUserData>> SearchAsync(
string sorting, string sorting = null,
string filter, string filter = null,
int maxResultCount, int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var result = await UserLookupAppService.SearchAsync( var result = await UserLookupAppService.SearchAsync(
new UserLookupSearchInputDto new UserLookupSearchInputDto
{ {
Filter = filter, Filter = filter,
MaxResultCount = maxResultCount MaxResultCount = maxResultCount,
SkipCount = skipCount,
Sorting = sorting
} }
); );
return result.Items.Cast<IUserData>().ToList(); return result.Items.Cast<IUserData>().ToList();
} }
public async Task<long> GetCountAsync(
string filter = null,
CancellationToken cancellationToken = new CancellationToken())
{
return await UserLookupAppService
.GetCountAsync(
new UserLookupCountInputDto
{
Filter = filter
}
);
}
} }
} }

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

@ -40,5 +40,12 @@ namespace Volo.Abp.Identity
{ {
return LookupAppService.SearchAsync(input); return LookupAppService.SearchAsync(input);
} }
[HttpGet]
[Route("count")]
public Task<long> GetCountAsync(UserLookupCountInputDto input)
{
return LookupAppService.GetCountAsync(input);
}
} }
} }

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

@ -15,15 +15,13 @@ namespace Volo.Abp.Identity.MongoDB
{ {
public class MongoIdentityUserRepository : MongoDbRepository<IAbpIdentityMongoDbContext, IdentityUser, Guid>, IIdentityUserRepository public class MongoIdentityUserRepository : MongoDbRepository<IAbpIdentityMongoDbContext, IdentityUser, Guid>, IIdentityUserRepository
{ {
private readonly IGuidGenerator _guidGenerator; public MongoIdentityUserRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider)
public MongoIdentityUserRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider, IGuidGenerator guidGenerator)
: base(dbContextProvider) : base(dbContextProvider)
{ {
} }
public virtual async Task<IdentityUser> FindByNormalizedUserNameAsync( public virtual async Task<IdentityUser> FindByNormalizedUserNameAsync(
string normalizedUserName, string normalizedUserName,
bool includeDetails = true, bool includeDetails = true,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
@ -35,7 +33,7 @@ namespace Volo.Abp.Identity.MongoDB
} }
public virtual async Task<List<string>> GetRoleNamesAsync( public virtual async Task<List<string>> GetRoleNamesAsync(
Guid id, Guid id,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken)); var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken));
@ -77,8 +75,8 @@ namespace Volo.Abp.Identity.MongoDB
} }
public virtual async Task<IdentityUser> FindByLoginAsync( public virtual async Task<IdentityUser> FindByLoginAsync(
string loginProvider, string loginProvider,
string providerKey, string providerKey,
bool includeDetails = true, bool includeDetails = true,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
@ -106,7 +104,7 @@ namespace Volo.Abp.Identity.MongoDB
} }
public virtual async Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync( public virtual async Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
string normalizedRoleName, string normalizedRoleName,
bool includeDetails = false, bool includeDetails = false,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
@ -135,7 +133,9 @@ namespace Volo.Abp.Identity.MongoDB
!filter.IsNullOrWhiteSpace(), !filter.IsNullOrWhiteSpace(),
u => u =>
u.UserName.Contains(filter) || u.UserName.Contains(filter) ||
u.Email.Contains(filter) u.Email.Contains(filter) ||
(u.Name != null && u.Name.Contains(filter)) ||
(u.Surname != null && u.Surname.Contains(filter))
) )
.OrderBy(sorting ?? nameof(IdentityUser.UserName)) .OrderBy(sorting ?? nameof(IdentityUser.UserName))
.As<IMongoQueryable<IdentityUser>>() .As<IMongoQueryable<IdentityUser>>()
@ -184,7 +184,9 @@ namespace Volo.Abp.Identity.MongoDB
!filter.IsNullOrWhiteSpace(), !filter.IsNullOrWhiteSpace(),
u => u =>
u.UserName.Contains(filter) || u.UserName.Contains(filter) ||
u.Email.Contains(filter) u.Email.Contains(filter) ||
(u.Name != null && u.Name.Contains(filter)) ||
(u.Surname != null && u.Surname.Contains(filter))
) )
.LongCountAsync(GetCancellationToken(cancellationToken)); .LongCountAsync(GetCancellationToken(cancellationToken));
} }
@ -227,4 +229,4 @@ namespace Volo.Abp.Identity.MongoDB
; ;
} }
} }
} }

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

@ -12,9 +12,15 @@ namespace Volo.Abp.Users
Task<IUserData> FindByUserNameAsync(string userName, CancellationToken cancellationToken = default); Task<IUserData> FindByUserNameAsync(string userName, CancellationToken cancellationToken = default);
Task<List<IUserData>> SearchAsync( Task<List<IUserData>> SearchAsync(
string sorting, string sorting = null,
string filter, string filter = null,
int maxResultCount, int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
Task<long> GetCountAsync(
string filter = null,
CancellationToken cancellationToken = default
);
} }
} }

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

@ -8,14 +8,25 @@ namespace Volo.Abp.Users
public interface IUserLookupService<TUser> public interface IUserLookupService<TUser>
where TUser : class, IUser where TUser : class, IUser
{ {
Task<TUser> FindByIdAsync(Guid id, CancellationToken cancellationToken = default); Task<TUser> FindByIdAsync(
Guid id,
CancellationToken cancellationToken = default
);
Task<TUser> FindByUserNameAsync(string userName, CancellationToken cancellationToken = default); Task<TUser> FindByUserNameAsync(
string userName,
CancellationToken cancellationToken = default
);
Task<List<IUserData>> SearchAsync( Task<List<IUserData>> SearchAsync(
string sorting, string sorting = null,
string filter, string filter = null,
int maxResultCount, int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default);
Task<long> GetCountAsync(
string filter = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
} }
} }

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

@ -21,5 +21,9 @@ namespace Volo.Abp.Users
string filter = null, string filter = null,
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
); );
Task<long> GetCountAsync(
string filter = null,
CancellationToken cancellationToken = default);
} }
} }

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

@ -140,15 +140,57 @@ namespace Volo.Abp.Users
return await _userRepository.FindAsync(externalUser.Id, cancellationToken: cancellationToken); return await _userRepository.FindAsync(externalUser.Id, cancellationToken: cancellationToken);
} }
public async Task<List<IUserData>> SearchAsync(string sorting, string filter, int maxResultCount, CancellationToken cancellationToken = default) public async Task<List<IUserData>> SearchAsync(
string sorting = null,
string filter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{ {
if (ExternalUserLookupServiceProvider != null) if (ExternalUserLookupServiceProvider != null)
{ {
return await ExternalUserLookupServiceProvider.SearchAsync(sorting, filter, maxResultCount, cancellationToken); return await ExternalUserLookupServiceProvider
.SearchAsync(
sorting,
filter,
maxResultCount,
skipCount,
cancellationToken
);
}
var localUsers = await _userRepository
.SearchAsync(
sorting,
maxResultCount,
skipCount,
filter,
cancellationToken
);
return localUsers
.Cast<IUserData>()
.ToList();
}
public async Task<long> GetCountAsync(
string filter = null,
CancellationToken cancellationToken = default)
{
if (ExternalUserLookupServiceProvider != null)
{
return await ExternalUserLookupServiceProvider
.GetCountAsync(
filter,
cancellationToken
);
} }
var localUsers = await _userRepository.SearchAsync(sorting, maxResultCount, 0, filter, cancellationToken); return await _userRepository
return localUsers.Cast<IUserData>().ToList(); .GetCountAsync(
filter,
cancellationToken
);
} }
protected abstract TUser CreateUser(IUserData externalUser); protected abstract TUser CreateUser(IUserData externalUser);

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

@ -32,7 +32,7 @@ namespace Volo.Abp.Users.EntityFrameworkCore
public async Task<List<TUser>> SearchAsync( public async Task<List<TUser>> SearchAsync(
string sorting = null, string sorting = null,
int maxResultCount = Int32.MaxValue, int maxResultCount = int.MaxValue,
int skipCount = 0, int skipCount = 0,
string filter = null, string filter = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
@ -42,11 +42,29 @@ namespace Volo.Abp.Users.EntityFrameworkCore
!filter.IsNullOrWhiteSpace(), !filter.IsNullOrWhiteSpace(),
u => u =>
u.UserName.Contains(filter) || u.UserName.Contains(filter) ||
u.Email.Contains(filter) u.Email.Contains(filter) ||
u.Name.Contains(filter) ||
u.Surname.Contains(filter)
) )
.OrderBy(sorting ?? nameof(IUser.UserName)) .OrderBy(sorting ?? nameof(IUser.UserName))
.PageBy(skipCount, maxResultCount) .PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
} }
public async Task<long> GetCountAsync(
string filter = null,
CancellationToken cancellationToken = default)
{
return await DbSet
.WhereIf(
!filter.IsNullOrWhiteSpace(),
u =>
u.UserName.Contains(filter) ||
u.Email.Contains(filter) ||
u.Name.Contains(filter) ||
u.Surname.Contains(filter)
)
.LongCountAsync(GetCancellationToken(cancellationToken));
}
} }
} }

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

@ -31,7 +31,11 @@ namespace Volo.Abp.Users.MongoDB
return await GetMongoQueryable().Where(u => ids.Contains(u.Id)).ToListAsync(GetCancellationToken(cancellationToken)); 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, public async Task<List<TUser>> SearchAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return await GetMongoQueryable() return await GetMongoQueryable()
@ -39,12 +43,28 @@ namespace Volo.Abp.Users.MongoDB
!filter.IsNullOrWhiteSpace(), !filter.IsNullOrWhiteSpace(),
u => u =>
u.UserName.Contains(filter) || u.UserName.Contains(filter) ||
u.Email.Contains(filter) u.Email.Contains(filter) ||
u.Name.Contains(filter) ||
u.Surname.Contains(filter)
) )
.OrderBy(sorting ?? nameof(IUserData.UserName)) .OrderBy(sorting ?? nameof(IUserData.UserName))
.As<IMongoQueryable<TUser>>() .As<IMongoQueryable<TUser>>()
.PageBy<TUser, IMongoQueryable<TUser>>(skipCount, maxResultCount) .PageBy<TUser, IMongoQueryable<TUser>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
} }
public async Task<long> GetCountAsync(string filter = null, CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.WhereIf<TUser, IMongoQueryable<TUser>>(
!filter.IsNullOrWhiteSpace(),
u =>
u.UserName.Contains(filter) ||
u.Email.Contains(filter) ||
u.Name.Contains(filter) ||
u.Surname.Contains(filter)
)
.LongCountAsync(GetCancellationToken(cancellationToken));
}
} }
} }

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

@ -2,11 +2,12 @@ import { Injectable, NgZone, Optional, SkipSelf } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { Actions, ofActionSuccessful, Store } from '@ngxs/store'; import { Actions, ofActionSuccessful, Store } from '@ngxs/store';
import { noop, Observable } from 'rxjs'; import { noop, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SetLanguage } from '../actions/session.actions'; import { SetLanguage } from '../actions/session.actions';
import { ApplicationConfiguration } from '../models/application-configuration';
import { Config } from '../models/config'; import { Config } from '../models/config';
import { ConfigState } from '../states/config.state'; import { ConfigState } from '../states/config.state';
import { registerLocale } from '../utils/initial-utils'; import { registerLocale } from '../utils/initial-utils';
import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils';
type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean; type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean;
@ -19,6 +20,10 @@ export class LocalizationService {
return this.store.selectSnapshot(state => state.SessionState.language); return this.store.selectSnapshot(state => state.SessionState.language);
} }
get languageChange(): Observable<SetLanguage> {
return this.actions.pipe(ofActionSuccessful(SetLanguage));
}
constructor( constructor(
private actions: Actions, private actions: Actions,
private store: Store, private store: Store,
@ -34,9 +39,7 @@ export class LocalizationService {
} }
private listenToSetLanguage() { private listenToSetLanguage() {
this.actions this.languageChange.subscribe(({ payload }) => this.registerLocale(payload));
.pipe(ofActionSuccessful(SetLanguage))
.subscribe(({ payload }) => this.registerLocale(payload));
} }
setRouteReuse(reuse: ShouldReuseRoute) { setRouteReuse(reuse: ShouldReuseRoute) {
@ -77,30 +80,31 @@ export class LocalizationService {
return this.store.selectSnapshot(ConfigState.getLocalization(key, ...interpolateParams)); return this.store.selectSnapshot(ConfigState.getLocalization(key, ...interpolateParams));
} }
isLocalized(key, sourceName) { localize(resourceName: string, key: string, defaultValue: string): Observable<string> {
if (sourceName === '_') { return this.store.select(ConfigState.getOne('localization')).pipe(
// A convention to suppress the localization map(createLocalizer),
return true; map(localize => localize(resourceName, key, defaultValue)),
} );
}
const localization = this.store.selectSnapshot(
ConfigState.getOne('localization'),
) as ApplicationConfiguration.Localization;
sourceName = sourceName || localization.defaultResourceName;
if (!sourceName) {
return false;
}
const source = localization.values[sourceName]; localizeSync(resourceName: string, key: string, defaultValue: string): string {
if (!source) { const localization = this.store.selectSnapshot(ConfigState.getOne('localization'));
return false; return createLocalizer(localization)(resourceName, key, defaultValue);
} }
const value = source[key]; localizeWithFallback(
if (value === undefined) { resourceNames: string[],
return false; keys: string[],
} defaultValue: string,
): Observable<string> {
return this.store.select(ConfigState.getOne('localization')).pipe(
map(createLocalizerWithFallback),
map(localizeWithFallback => localizeWithFallback(resourceNames, keys, defaultValue)),
);
}
return true; localizeWithFallbackSync(resourceNames: string[], keys: string[], defaultValue: string): string {
const localization = this.store.selectSnapshot(ConfigState.getOne('localization'));
return createLocalizerWithFallback(localization)(resourceNames, keys, defaultValue);
} }
} }

140
npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts

@ -0,0 +1,140 @@
import {
createLocalizationPipeKeyGenerator,
createLocalizer,
createLocalizerWithFallback,
} from '../utils/localization-utils';
describe('Localization Utils', () => {
describe('#createLocalizer', () => {
const localize = createLocalizer({
values: { foo: { bar: 'baz' }, x: { y: 'z' } },
defaultResourceName: 'x',
currentCulture: null,
languages: [],
});
test.each`
resource | key | defaultValue | expected
${'_'} | ${'TEST'} | ${'DEFAULT'} | ${'TEST'}
${'foo'} | ${'bar'} | ${'DEFAULT'} | ${'baz'}
${'x'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${'y'} | ${'DEFAULT'} | ${'z'}
${'a'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
`(
'should return $expected when resource name is $resource and key is $key',
({ resource, key, defaultValue, expected }) => {
const result = localize(resource, key, defaultValue);
expect(result).toBe(expected);
},
);
});
describe('#createLocalizerWithFallback', () => {
const localizeWithFallback = createLocalizerWithFallback({
values: { foo: { bar: 'baz' }, x: { y: 'z' } },
defaultResourceName: 'x',
currentCulture: null,
languages: [],
});
test.each`
resources | keys | defaultValue | expected
${['', '_']} | ${['TEST', 'OTHER']} | ${'DEFAULT'} | ${'TEST'}
${['foo']} | ${['bar']} | ${'DEFAULT'} | ${'baz'}
${['x']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['foo']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['x']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['a', 'b', 'c']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${[]} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['foo']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'baz'}
${['x']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['a', 'b', 'c']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${[]} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['foo']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['x']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['foo']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['x']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
`(
'should return $expected when resource names are $resources and keys are $keys',
({ resources, keys, defaultValue, expected }) => {
const result = localizeWithFallback(resources, keys, defaultValue);
expect(result).toBe(expected);
},
);
});
describe('#createLocalizationPipeKeyGenerator', () => {
const generateLocalizationPipeKey = createLocalizationPipeKeyGenerator({
values: { foo: { bar: 'baz' }, x: { y: 'z' } },
defaultResourceName: 'x',
currentCulture: null,
languages: [],
});
test.each`
resources | keys | defaultKey | expected
${['', '_']} | ${['TEST', 'OTHER']} | ${'DEFAULT'} | ${'TEST'}
${['foo']} | ${['bar']} | ${'DEFAULT'} | ${'foo::bar'}
${['x']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['foo']} | ${['y']} | ${'DEFAULT'} | ${'x::y'}
${['x']} | ${['y']} | ${'DEFAULT'} | ${'x::y'}
${['a', 'b', 'c']} | ${['y']} | ${'DEFAULT'} | ${'x::y'}
${['']} | ${['y']} | ${'DEFAULT'} | ${'x::y'}
${[]} | ${['y']} | ${'DEFAULT'} | ${'x::y'}
${['foo']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'foo::bar'}
${['x']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'x::y'}
${['a', 'b', 'c']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'x::y'}
${['']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'x::y'}
${[]} | ${['bar', 'y']} | ${'DEFAULT'} | ${'x::y'}
${['foo']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['x']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['foo']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['x']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
`(
'should return $expected when resource names are $resources and keys are $keys',
({ resources, keys, defaultKey, expected }) => {
const result = generateLocalizationPipeKey(resources, keys, defaultKey);
expect(result).toBe(expected);
},
);
});
});

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

@ -1,7 +1,7 @@
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { Store, Actions } from '@ngxs/store'; import { Actions, Store } from '@ngxs/store';
import { Observable, of, Subject } from 'rxjs'; import { of, Subject } from 'rxjs';
import { LocalizationService } from '../services/localization.service'; import { LocalizationService } from '../services/localization.service';
describe('LocalizationService', () => { describe('LocalizationService', () => {
@ -75,4 +75,176 @@ describe('LocalizationService', () => {
} }
}); });
}); });
describe('#localize', () => {
test.each`
resource | key | defaultValue | expected
${'_'} | ${'TEST'} | ${'DEFAULT'} | ${'TEST'}
${'foo'} | ${'bar'} | ${'DEFAULT'} | ${'baz'}
${'x'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${'y'} | ${'DEFAULT'} | ${'z'}
${'a'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
`(
'should return observable $expected when resource name is $resource and key is $key',
async ({ resource, key, defaultValue, expected }) => {
store.select.andReturn(
of({
values: { foo: { bar: 'baz' }, x: { y: 'z' } },
defaultResourceName: 'x',
}),
);
const result = await service.localize(resource, key, defaultValue).toPromise();
expect(result).toBe(expected);
},
);
});
describe('#localizeSync', () => {
test.each`
resource | key | defaultValue | expected
${'_'} | ${'TEST'} | ${'DEFAULT'} | ${'TEST'}
${'foo'} | ${'bar'} | ${'DEFAULT'} | ${'baz'}
${'x'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${'y'} | ${'DEFAULT'} | ${'z'}
${'a'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${''} | ${'DEFAULT'} | ${'DEFAULT'}
${'foo'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${'x'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${'a'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${''} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
${undefined} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'}
`(
'should return $expected when resource name is $resource and key is $key',
({ resource, key, defaultValue, expected }) => {
store.selectSnapshot.andReturn({
values: { foo: { bar: 'baz' }, x: { y: 'z' } },
defaultResourceName: 'x',
});
const result = service.localizeSync(resource, key, defaultValue);
expect(result).toBe(expected);
},
);
});
describe('#localizeWithFallback', () => {
test.each`
resources | keys | defaultValue | expected
${['', '_']} | ${['TEST', 'OTHER']} | ${'DEFAULT'} | ${'TEST'}
${['foo']} | ${['bar']} | ${'DEFAULT'} | ${'baz'}
${['x']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['foo']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['x']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['a', 'b', 'c']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${[]} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['foo']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'baz'}
${['x']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['a', 'b', 'c']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${[]} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['foo']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['x']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['foo']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['x']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
`(
'should return observable $expected when resource names are $resources and keys are $keys',
async ({ resources, keys, defaultValue, expected }) => {
store.select.andReturn(
of({
values: { foo: { bar: 'baz' }, x: { y: 'z' } },
defaultResourceName: 'x',
}),
);
const result = await service
.localizeWithFallback(resources, keys, defaultValue)
.toPromise();
expect(result).toBe(expected);
},
);
});
describe('#localizeWithFallbackSync', () => {
test.each`
resources | keys | defaultValue | expected
${['', '_']} | ${['TEST', 'OTHER']} | ${'DEFAULT'} | ${'TEST'}
${['foo']} | ${['bar']} | ${'DEFAULT'} | ${'baz'}
${['x']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'}
${['foo']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['x']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['a', 'b', 'c']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['']} | ${['y']} | ${'DEFAULT'} | ${'z'}
${[]} | ${['y']} | ${'DEFAULT'} | ${'z'}
${['foo']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'baz'}
${['x']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['a', 'b', 'c']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${[]} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'}
${['foo']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['x']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'}
${['foo']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['x']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['a', 'b', 'c']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${['']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
${[]} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'}
`(
'should return $expected when resource names are $resources and keys are $keys',
({ resources, keys, defaultValue, expected }) => {
store.selectSnapshot.andReturn({
values: { foo: { bar: 'baz' }, x: { y: 'z' } },
defaultResourceName: 'x',
});
const result = service.localizeWithFallbackSync(resources, keys, defaultValue);
expect(result).toBe(expected);
},
);
});
}); });

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

@ -3,6 +3,7 @@ export * from './form-utils';
export * from './generator-utils'; export * from './generator-utils';
export * from './initial-utils'; export * from './initial-utils';
export * from './lazy-load-utils'; export * from './lazy-load-utils';
export * from './localization-utils';
export * from './number-utils'; export * from './number-utils';
export * from './route-utils'; export * from './route-utils';
export * from './rxjs-utils'; export * from './rxjs-utils';

56
npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts

@ -0,0 +1,56 @@
import { ApplicationConfiguration } from '../models/application-configuration';
export function createLocalizer(localization: ApplicationConfiguration.Localization) {
return (resourceName: string, key: string, defaultValue: string) => {
if (resourceName === '_') return key;
const resource = localization.values[resourceName];
if (!resource) return defaultValue;
return resource[key] || defaultValue;
};
}
export function createLocalizerWithFallback(localization: ApplicationConfiguration.Localization) {
const findLocalization = createLocalizationFinder(localization);
return (resourceNames: string[], keys: string[], defaultValue: string) => {
const { localized } = findLocalization(resourceNames, keys);
return localized || defaultValue;
};
}
export function createLocalizationPipeKeyGenerator(
localization: ApplicationConfiguration.Localization,
) {
const findLocalization = createLocalizationFinder(localization);
return (resourceNames: string[], keys: string[], defaultKey: string) => {
const { resourceName, key } = findLocalization(resourceNames, keys);
return !resourceName ? defaultKey : resourceName === '_' ? key : `${resourceName}::${key}`;
};
}
function createLocalizationFinder(localization: ApplicationConfiguration.Localization) {
const localize = createLocalizer(localization);
return (resourceNames: string[], keys: string[]) => {
resourceNames = resourceNames.concat(localization.defaultResourceName).filter(Boolean);
const resourceCount = resourceNames.length;
const keyCount = keys.length;
for (let i = 0; i < resourceCount; i++) {
const resourceName = resourceNames[i];
for (let j = 0; j < keyCount; j++) {
const key = keys[j];
const localized = localize(resourceName, key, null);
if (localized) return { resourceName, key, localized };
}
}
return { resourceName: undefined, key: undefined, localized: undefined };
};
}

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

@ -30,10 +30,6 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
@Input() @Input()
color = '#77b6ff'; color = '#77b6ff';
@Input()
filter = (action: StartLoader | StopLoader) =>
action.payload.url.indexOf('openid-configuration') < 0;
@Input() @Input()
isLoading = false; isLoading = false;
@ -47,6 +43,10 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
stopDelay = 800; stopDelay = 800;
@Input()
filter = (action: StartLoader | StopLoader) =>
action.payload.url.indexOf('openid-configuration') < 0;
private readonly clearProgress = () => { private readonly clearProgress = () => {
this.progressLevel = 0; this.progressLevel = 0;
this.cdRef.detectChanges(); this.cdRef.detectChanges();

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

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

10
templates/app/aspnet-core/test/MyCompanyName.MyProjectName.TestBase/Security/FakeCurrentPrincipalAccessor.cs

@ -6,9 +6,13 @@ using Volo.Abp.Security.Claims;
namespace MyCompanyName.MyProjectName.Security namespace MyCompanyName.MyProjectName.Security
{ {
[Dependency(ReplaceServices = true)] [Dependency(ReplaceServices = true)]
public class FakeCurrentPrincipalAccessor : ICurrentPrincipalAccessor, ISingletonDependency public class FakeCurrentPrincipalAccessor : ThreadCurrentPrincipalAccessor
{ {
public ClaimsPrincipal Principal => GetPrincipal(); public override ClaimsPrincipal GetClaimsPrincipal()
{
return GetPrincipal();
}
private ClaimsPrincipal _principal; private ClaimsPrincipal _principal;
private ClaimsPrincipal GetPrincipal() private ClaimsPrincipal GetPrincipal()
@ -36,4 +40,4 @@ namespace MyCompanyName.MyProjectName.Security
return _principal; return _principal;
} }
} }
} }

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

Loading…
Cancel
Save