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",
"SuccessfullyDeleted": "Successfully 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
* `--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:
* `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.
@ -51,10 +51,10 @@ abp new Acme.BookStore
* `--mobile` or `-m`: Specifies the mobile application framework. Default framework is `react-native`. Available frameworks:
* `none`: no mobile application.
* `react-native`: React Native.
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). Additional options:
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
* **`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).
* `--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.

21
docs/en/Dapper.md

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

8
docs/en/Object-Extensions.md

@ -200,17 +200,17 @@ ObjectExtensionManager.Instance
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, int>(
"MyIntProperty",
.AddOrUpdateProperty<IdentityUser, DateTime>(
"MyDateTimeProperty",
options =>
{
options.DefaultValueFactory = () => 42;
options.DefaultValueFactory = () => DateTime.Now;
});
````
`options.DefaultValueFactory` has a higher priority than the `options.DefaultValue` .
> Tip: Use `DefaultValueFactory` option only if the default value may change over the time. 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

14
docs/en/SignalR-Integration.md

@ -1,6 +1,6 @@
# 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
@ -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.
![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:
````
<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:
````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:
@ -234,6 +235,7 @@ var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name
new PasswordResetModel
{
Name = "john",
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:
````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.

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

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

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

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

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

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

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
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.TagHelpers.Extensions;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Localization;
using Volo.Abp.Reflection;
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 HtmlEncoder _encoder;
private readonly IAbpTagHelperLocalizer _tagHelperLocalizer;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
public AbpSelectTagHelperService(IHtmlGenerator generator, HtmlEncoder encoder, IAbpTagHelperLocalizer tagHelperLocalizer)
public AbpSelectTagHelperService(
IHtmlGenerator generator,
HtmlEncoder encoder,
IAbpTagHelperLocalizer tagHelperLocalizer,
IStringLocalizerFactory stringLocalizerFactory)
{
_generator = generator;
_encoder = encoder;
_tagHelperLocalizer = tagHelperLocalizer;
_stringLocalizerFactory = stringLocalizerFactory;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -209,74 +217,33 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
}
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];
if (!localizedString.ResourceNotFound)
var memberName = enumType.GetEnumName(enumValue);
var localizedMemberName = AbpInternalLocalizationHelper.LocalizeWithFallback(
new[]
{
containerLocalizer,
_stringLocalizerFactory.CreateDefaultOrNull()
},
new[]
{
$"Enum:{enumType.Name}.{memberName}",
$"{enumType.Name}.{memberName}",
memberName
},
memberName
);
selectItems.Add(new SelectListItem
{
return localizedString.Value;
}
}
//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;
}
Value = enumValue.ToString(),
Text = localizedMemberName
});
}
return fieldName;
return selectItems;
}
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.Modularity;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker
{
[DependsOn(typeof(JQueryScriptContributor))]
public class BootstrapDatepickerScriptContributor : BundleContributor

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

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

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

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

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

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

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

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

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

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

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

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

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 List<Type> ObjectResultTypes { get; }
public static List<Type> ObjectResultTypes { get; }
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);
if (!excludeTypes.IsNullOrEmpty() && excludeTypes.Any(t => t.IsAssignableFrom(returnType)))
{
return false;
}
if (!typeof(IActionResult).IsAssignableFrom(returnType))
{
return true;
@ -32,4 +37,4 @@ namespace Volo.Abp.AspNetCore.Mvc
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
{
Modules = new Dictionary<string, ModuleExtensionDto>()
Modules = new Dictionary<string, ModuleExtensionDto>(),
Enums = new Dictionary<string, ExtensionEnumDto>()
};
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);
}
FillEnums(objectExtensionsDto);
return objectExtensionsDto;
}
@ -104,9 +107,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
var extensionPropertyDto = new ExtensionPropertyDto
{
Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyConfig.Type),
TypeSimple = propertyConfig.Type.IsEnum
? "enum"
: TypeHelper.GetSimplifiedName(propertyConfig.Type),
TypeSimple = GetSimpleTypeName(propertyConfig),
Attributes = new List<ExtensionPropertyAttributeDto>(),
DisplayName = CreateDisplayNameDto(propertyConfig),
Configuration = new Dictionary<string, object>(),
@ -158,6 +159,26 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
return extensionPropertyDto;
}
protected virtual string GetSimpleTypeName(ExtensionPropertyConfiguration propertyConfig)
{
if (propertyConfig.Type.IsEnum)
{
return "enum";
}
if (propertyConfig.IsDate())
{
return "date";
}
if (propertyConfig.IsDateTime())
{
return "datetime";
}
return TypeHelper.GetSimplifiedName(propertyConfig.Type);
}
protected virtual LocalizableStringDto CreateDisplayNameDto(ExtensionPropertyConfiguration propertyConfig)
{
if (propertyConfig.DisplayName == null)
@ -183,5 +204,45 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
return null;
}
protected virtual void FillEnums(ObjectExtensionsDto objectExtensionsDto)
{
var enumProperties = ObjectExtensionManager.Instance.Modules().Values
.SelectMany(
m => m.Entities.Values.SelectMany(
e => e.GetProperties()
)
)
.Where(p => p.Type.IsEnum)
.ToList();
foreach (var enumProperty in enumProperties)
{
// ReSharper disable once AssignNullToNotNullAttribute (enumProperty.Type.FullName can not be null for this case)
objectExtensionsDto.Enums[enumProperty.Type.FullName] = CreateExtensionEnumDto(enumProperty);
}
}
protected virtual ExtensionEnumDto CreateExtensionEnumDto(ExtensionPropertyConfiguration enumProperty)
{
var extensionEnumDto = new ExtensionEnumDto
{
Fields = new List<ExtensionEnumFieldDto>(),
LocalizationResource = enumProperty.GetLocalizationResourceNameOrNull()
};
foreach (var enumValue in enumProperty.Type.GetEnumValues())
{
extensionEnumDto.Fields.Add(
new ExtensionEnumFieldDto
{
Name = enumProperty.Type.GetEnumName(enumValue),
Value = enumValue
}
);
}
return extensionEnumDto;
}
}
}

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

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

@ -1,5 +1,4 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.ObjectExtending;
@ -61,7 +60,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
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
{
IsObjectResult = ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType)
IsObjectResult = ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType, typeof(void))
};
if (unitOfWorkAttr?.IsDisabled == true)
@ -71,7 +71,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
}
}
}
private AbpUnitOfWorkOptions CreateOptions(PageHandlerExecutingContext context, UnitOfWorkAttribute unitOfWorkAttribute)
{
var options = new AbpUnitOfWorkOptions();
@ -102,4 +102,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
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.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace Volo.Abp.ObjectExtending
{
public static class ObjectExtensionPropertyInfoAspNetCoreMvcExtensions
{
private static readonly HashSet<Type> NumberTypes = new HashSet<Type> {
typeof(int),
typeof(long),
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(int?),
typeof(long?),
typeof(byte?),
typeof(sbyte?),
typeof(short?),
typeof(ushort?),
typeof(uint?),
typeof(long?),
typeof(ulong?),
typeof(float?),
typeof(double?),
private static readonly Type[] DateTimeTypes =
{
typeof(DateTime),
typeof(DateTimeOffset)
};
public static string GetInputType(this ObjectExtensionPropertyInfo propertyInfo)
public static bool IsDate(this IBasicObjectExtensionPropertyInfo property)
{
foreach (var attribute in propertyInfo.Attributes)
{
var inputTypeByAttribute = GetInputTypeFromAttributeOrNull(attribute);
if (inputTypeByAttribute != null)
{
return inputTypeByAttribute;
}
}
return GetInputTypeFromTypeOrNull(propertyInfo.Type)
?? "text"; //default
return DateTimeTypes.Contains(property.Type) &&
property.GetDataTypeOrNull() == DataType.Date;
}
private static string GetInputTypeFromAttributeOrNull(Attribute attribute)
public static bool IsDateTime(this IBasicObjectExtensionPropertyInfo property)
{
if (attribute is EmailAddressAttribute)
{
return "email";
}
if (attribute is UrlAttribute)
{
return "url";
}
if (attribute is HiddenInputAttribute)
{
return "hidden";
}
if (attribute is PhoneAttribute)
{
return "tel";
}
if (attribute is DataTypeAttribute dataTypeAttribute)
{
switch (dataTypeAttribute.DataType)
{
case DataType.Password:
return "password";
case DataType.Date:
return "date";
case DataType.Time:
return "time";
case DataType.EmailAddress:
return "email";
case DataType.Url:
return "url";
case DataType.PhoneNumber:
return "tel";
case DataType.DateTime:
return "datetime-local";
}
}
return null;
return DateTimeTypes.Contains(property.Type) &&
!property.IsDate();
}
private static string GetInputTypeFromTypeOrNull(Type type)
public static DataType? GetDataTypeOrNull(this IBasicObjectExtensionPropertyInfo property)
{
if (type == typeof(bool))
{
return "checkbox";
}
if (type == typeof(DateTime))
{
return "datetime-local";
}
if (NumberTypes.Contains(type))
{
return "number";
}
return null;
return property
.Attributes
.OfType<DataTypeAttribute>()
.FirstOrDefault()?.DataType;
}
}
}

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

@ -1,21 +1,28 @@
using Microsoft.AspNetCore.SignalR;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.SignalR
{
public class AbpSignalRUserIdProvider : IUserIdProvider, ITransientDependency
{
public ICurrentUser CurrentUser { get; }
public AbpSignalRUserIdProvider(ICurrentUser currentUser)
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
private readonly ICurrentUser _currentUser;
public AbpSignalRUserIdProvider(ICurrentPrincipalAccessor currentPrincipalAccessor, ICurrentUser currentUser)
{
CurrentUser = currentUser;
_currentPrincipalAccessor = currentPrincipalAccessor;
_currentUser = currentUser;
}
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 override ClaimsPrincipal Principal => _httpContextAccessor.HttpContext?.User ?? base.Principal;
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextCurrentPrincipalAccessor(IHttpContextAccessor 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.ProjectBuilding;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Templates.Console;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
@ -266,6 +267,7 @@ namespace Volo.Abp.Cli.Commands
protected virtual MobileApp GetMobilePreference(CommandLineArgs commandLineArgs)
{
var optionValue = commandLineArgs.Options.GetOrNull(Options.Mobile.Short, Options.Mobile.Long);
var template = commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long);
switch (optionValue)
{
case "none":
@ -273,7 +275,7 @@ namespace Volo.Abp.Cli.Commands
case "react-native":
return MobileApp.ReactNative;
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 Volo.Abp.Cli.Http;
using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectBuilding.Templates.Console;
using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule;
using Volo.Abp.DependencyInjection;
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(string.Empty);
Logger.LogWarning("Find the following template in your cache directory: ");
Logger.LogWarning("\t Template Name\tVersion");
Logger.LogWarning("\tTemplate Name\tVersion");
var templateList = GetLocalTemplates();
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);
@ -241,7 +242,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
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)
{
templateList.Add((match.Groups[1].Value, match.Groups[2].Value));
@ -278,4 +279,4 @@ namespace Volo.Abp.Cli.ProjectBuilding
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 Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectBuilding.Templates.Console;
using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule;
using Volo.Abp.DependencyInjection;
@ -25,9 +26,11 @@ namespace Volo.Abp.Cli.ProjectBuilding
return new ModuleTemplate();
case ModuleProTemplate.TemplateName:
return new ModuleProTemplate();
case ConsoleTemplate.TemplateName:
return new ConsoleTemplate();
default:
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
foreach (var module in modules.Where(m => m.Instance is IPreConfigureServices))
{
((IPreConfigureServices)module.Instance).PreConfigureServices(context);
try
{
((IPreConfigureServices)module.Instance).PreConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IPreConfigureServices.PreConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
//ConfigureServices
@ -119,13 +126,27 @@ namespace Volo.Abp.Modularity
}
}
module.Instance.ConfigureServices(context);
try
{
module.Instance.ConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IAbpModule.ConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
//PostConfigureServices
foreach (var module in modules.Where(m => m.Instance is IPostConfigureServices))
{
((IPostConfigureServices)module.Instance).PostConfigureServices(context);
try
{
((IPostConfigureServices)module.Instance).PostConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IPostConfigureServices.PostConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
foreach (var module in modules)

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

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

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

@ -2,14 +2,23 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Volo.Abp.Localization;
namespace Volo.Abp.Reflection
{
public static class TypeHelper
{
private static readonly HashSet<Type> FloatingTypes = new HashSet<Type>
{
typeof(float),
typeof(double),
typeof(decimal)
};
private static readonly HashSet<Type> NonNullablePrimitiveTypes = new HashSet<Type>
{
typeof(byte),
@ -40,7 +49,7 @@ namespace Volo.Abp.Reflection
{
return false;
}
var type = obj.GetType();
if (!type.GetTypeInfo().IsGenericType)
{
@ -297,9 +306,39 @@ namespace Volo.Abp.Reflection
public static object ConvertFromString(Type targetType, string value)
{
return TypeDescriptor
.GetConverter(targetType)
.ConvertFromString(value);
if (value == null)
{
return null;
}
var converter = TypeDescriptor.GetConverter(targetType);
if (IsFloatingType(targetType))
{
using (CultureHelper.Use(CultureInfo.InvariantCulture))
{
return converter.ConvertFromString(value.Replace(',', '.'));
}
}
return converter.ConvertFromString(value);
}
public static bool IsFloatingType(Type type, bool includeNullable = true)
{
if (FloatingTypes.Contains(type))
{
return true;
}
if (includeNullable &&
IsNullable(type) &&
FloatingTypes.Contains(type.GenericTypeArguments[0]))
{
return true;
}
return false;
}
public static object ConvertFrom<TTargetType>(object value)
@ -313,5 +352,12 @@ namespace Volo.Abp.Reflection
.GetConverter(targetType)
.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;
}
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);
if (value == null)
{
return null;
return property.GetDefaultValue();
}
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
{
public class ExtensionPropertyConfiguration : IHasNameWithLocalizableDisplayName
public class ExtensionPropertyConfiguration : IHasNameWithLocalizableDisplayName, IBasicObjectExtensionPropertyInfo
{
[NotNull]
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.Collections.Generic;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
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
{
public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName
public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName, IBasicObjectExtensionPropertyInfo
{
[NotNull]
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
{
public interface ICurrentPrincipalAccessor
{
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 Volo.Abp.DependencyInjection;
@ -6,6 +7,28 @@ namespace Volo.Abp.Security.Claims
{
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);
if (localizer != null)
{
scriptObject.Import(
"L",
new Func<string, string>(
name => localizer[name]
)
);
scriptObject.SetValue("L", new TemplateLocalizer(localizer), true);
}
context.PushGlobal(scriptObject);
@ -163,4 +158,4 @@ namespace Volo.Abp.TextTemplating
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 match the regular expression '{1}'.": "字段{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 {0} field is not a valid fully-qualified http, https, or ftp URL.": "字段{0}不是有效的完全限定的http,https或ftp URL.",
"The field {0} is invalid.": "字段{0}是无效值.",
@ -31,4 +31,4 @@
"ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "字段{0}不是有效的完全限定的http,https或ftp URL.",
"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);
}
}
}
}

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;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{
public class ExceptionTestPage : AbpPageModel
{
public void OnGetUserFriendlyException1()
public void OnGetUserFriendlyException_void()
{
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!");
}
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 Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -24,15 +24,33 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
}
[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);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("This is a sample exception!");
await Assert.ThrowsAsync<UserFriendlyException>(
async () => await GetResponseAsStringAsync(
"/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException_Void"
)
);
#pragma warning disable 4014
_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>());
#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()
{
await Assert.ThrowsAsync<UserFriendlyException>(
async () => await GetResponseAsObjectAsync<RemoteServiceErrorResponse>(
"/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException2"
async () => await GetResponseAsStringAsync(
"/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException_ActionResult"
)
);
@ -50,7 +68,35 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
_fakeExceptionSubscriber
.DidNotReceive()
.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
}
}
}
}

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")]
public void OnGetNotAllowedFeature()
public ObjectResult OnGetNotAllowedFeature()
{
return new ObjectResult(42);
}
public ObjectResult OnGetNoFeature()
@ -24,4 +24,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Features
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)]
public void OnGetHandledException()
public ObjectResult OnGetHandledException()
{
CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue();
throw new UserFriendlyException("This is a sample exception!");
}
public ObjectResult OnGetExceptionOnComplete()
{
CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse();
_testUnitOfWorkConfig.ThrowExceptionOnComplete = true;
//Prevent rendering of pages.
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(int)).ShouldBe(0);
TypeHelper.GetDefaultValue(typeof(string)).ShouldBeNull();
TypeHelper.GetDefaultValue(typeof(MyEnum)).ShouldBe(MyEnum.EnumValue0);
}
[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 =>
{
property.DefaultValue = "custom-value";
})
.AddOrUpdateProperty<MyExtensibleObject, MyTestEnum>("EnumProp", property =>
{
property.DefaultValue = MyTestEnum.EnumValue2;
});
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntProp")
.DefaultValue.ShouldBe(0);
.GetDefaultValue().ShouldBe(0);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntPropWithCustomDefaultValue")
.DefaultValue.ShouldBe(42);
.GetDefaultValue().ShouldBe(42);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("BoolProp")
.DefaultValue.ShouldBe(false);
.GetDefaultValue().ShouldBe(false);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntProp")
.DefaultValue.ShouldBeNull();
.GetDefaultValue().ShouldBeNull();
var propWithDefaultValueFactory = _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntPropWithCustomDefaultValueFactory");
@ -162,11 +166,15 @@ namespace Volo.Abp.ObjectExtending
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringProp")
.DefaultValue.ShouldBeNull();
.GetDefaultValue().ShouldBeNull();
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringPropWithCustomDefaultValue")
.DefaultValue.ShouldBe("custom-value");
.GetDefaultValue().ShouldBe("custom-value");
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("EnumProp")
.GetDefaultValue().ShouldBe(MyTestEnum.EnumValue2);
}
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",
"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",
"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(
TestTemplates.ForgotPasswordEmail,
new ForgotPasswordEmailModel("John"),
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(
TestTemplates.ForgotPasswordEmail,
model: new Dictionary<string, object>() { { "name", "John" } },
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
@ -104,5 +106,20 @@ namespace Volo.Abp.TextTemplating
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.Areas.Blog.Helpers.TagHelpers
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs
@using Volo.Blogging.SocialMedia
@inject IAuthorizationService Authorization
@inject IOptionsSnapshot<BloggingTwitterOptions> twitterOptions
@model DetailModel
@ -21,6 +22,11 @@
ViewBag.TwitterDescription = Model.Post.Description;
ViewBag.TwitterImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}";
ViewBag.LinkedInUrl = Request.GetEncodedUrl();
ViewBag.LinkedInTitle = Model.Post.Title;
ViewBag.LinkedInDescription = Model.Post.Description;
ViewBag.LinkedInImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}";
var hasCommentingPermission = CurrentUser.IsAuthenticated; //TODO: Apply real policy!
}
@section scripts {
@ -129,7 +135,7 @@
<h5>@L["TagsInThisArticle"]</h5>
@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>
}

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>
<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>
</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
{
[DependsOn(
typeof(DocsAdminApplicationContractsModule))]
typeof(DocsAdminApplicationContractsModule),
typeof(AbpHttpClientModule))]
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>
<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>
</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
{
[DependsOn(
typeof(DocsApplicationContractsModule))]
typeof(DocsApplicationContractsModule),
typeof(AbpHttpClientModule)
)]
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<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
{
public class UserLookupSearchInputDto : LimitedResultRequestDto, ISortedResultRequest
public class UserLookupSearchInputDto : PagedAndSortedResultRequestDto
{
public string Sorting { 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(
input.Sorting,
input.Filter,
input.MaxResultCount
input.MaxResultCount,
input.SkipCount
);
return new ListResultDto<UserData>(
@ -54,5 +55,10 @@ namespace Volo.Abp.Identity
.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(
string sorting,
string filter,
int maxResultCount,
string sorting = null,
string filter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
var users = await UserRepository.GetListAsync(
sorting: sorting,
maxResultCount: maxResultCount,
skipCount: skipCount,
filter: filter,
includeDetails: false,
cancellationToken: cancellationToken
@ -64,5 +66,12 @@ namespace Volo.Abp.Identity
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.UserName.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))
.PageBy(skipCount, maxResultCount)
@ -170,7 +171,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
!filter.IsNullOrWhiteSpace(),
u =>
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));
}

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(
string sorting,
string filter,
int maxResultCount,
string sorting = null,
string filter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
var result = await UserLookupAppService.SearchAsync(
new UserLookupSearchInputDto
{
Filter = filter,
MaxResultCount = maxResultCount
MaxResultCount = maxResultCount,
SkipCount = skipCount,
Sorting = sorting
}
);
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);
}
[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
{
private readonly IGuidGenerator _guidGenerator;
public MongoIdentityUserRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider, IGuidGenerator guidGenerator)
public MongoIdentityUserRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public virtual async Task<IdentityUser> FindByNormalizedUserNameAsync(
string normalizedUserName,
string normalizedUserName,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
@ -35,7 +33,7 @@ namespace Volo.Abp.Identity.MongoDB
}
public virtual async Task<List<string>> GetRoleNamesAsync(
Guid id,
Guid id,
CancellationToken cancellationToken = default)
{
var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken));
@ -77,8 +75,8 @@ namespace Volo.Abp.Identity.MongoDB
}
public virtual async Task<IdentityUser> FindByLoginAsync(
string loginProvider,
string providerKey,
string loginProvider,
string providerKey,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
@ -106,7 +104,7 @@ namespace Volo.Abp.Identity.MongoDB
}
public virtual async Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
string normalizedRoleName,
string normalizedRoleName,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
@ -135,7 +133,9 @@ namespace Volo.Abp.Identity.MongoDB
!filter.IsNullOrWhiteSpace(),
u =>
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))
.As<IMongoQueryable<IdentityUser>>()
@ -184,7 +184,9 @@ namespace Volo.Abp.Identity.MongoDB
!filter.IsNullOrWhiteSpace(),
u =>
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));
}
@ -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<List<IUserData>> SearchAsync(
string sorting,
string filter,
int maxResultCount,
string sorting = null,
string filter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
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>
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(
string sorting,
string filter,
int maxResultCount,
string sorting = null,
string filter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default);
Task<long> GetCountAsync(
string filter = null,
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,
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);
}
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)
{
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 localUsers.Cast<IUserData>().ToList();
return await _userRepository
.GetCountAsync(
filter,
cancellationToken
);
}
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(
string sorting = null,
int maxResultCount = Int32.MaxValue,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
CancellationToken cancellationToken = default)
@ -42,11 +42,29 @@ namespace Volo.Abp.Users.EntityFrameworkCore
!filter.IsNullOrWhiteSpace(),
u =>
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))
.PageBy(skipCount, maxResultCount)
.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));
}
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)
{
return await GetMongoQueryable()
@ -39,12 +43,28 @@ namespace Volo.Abp.Users.MongoDB
!filter.IsNullOrWhiteSpace(),
u =>
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))
.As<IMongoQueryable<TUser>>()
.PageBy<TUser, IMongoQueryable<TUser>>(skipCount, maxResultCount)
.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 { Actions, ofActionSuccessful, Store } from '@ngxs/store';
import { noop, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SetLanguage } from '../actions/session.actions';
import { ApplicationConfiguration } from '../models/application-configuration';
import { Config } from '../models/config';
import { ConfigState } from '../states/config.state';
import { registerLocale } from '../utils/initial-utils';
import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils';
type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean;
@ -19,6 +20,10 @@ export class LocalizationService {
return this.store.selectSnapshot(state => state.SessionState.language);
}
get languageChange(): Observable<SetLanguage> {
return this.actions.pipe(ofActionSuccessful(SetLanguage));
}
constructor(
private actions: Actions,
private store: Store,
@ -34,9 +39,7 @@ export class LocalizationService {
}
private listenToSetLanguage() {
this.actions
.pipe(ofActionSuccessful(SetLanguage))
.subscribe(({ payload }) => this.registerLocale(payload));
this.languageChange.subscribe(({ payload }) => this.registerLocale(payload));
}
setRouteReuse(reuse: ShouldReuseRoute) {
@ -77,30 +80,31 @@ export class LocalizationService {
return this.store.selectSnapshot(ConfigState.getLocalization(key, ...interpolateParams));
}
isLocalized(key, sourceName) {
if (sourceName === '_') {
// A convention to suppress the localization
return true;
}
const localization = this.store.selectSnapshot(
ConfigState.getOne('localization'),
) as ApplicationConfiguration.Localization;
sourceName = sourceName || localization.defaultResourceName;
if (!sourceName) {
return false;
}
localize(resourceName: string, key: string, defaultValue: string): Observable<string> {
return this.store.select(ConfigState.getOne('localization')).pipe(
map(createLocalizer),
map(localize => localize(resourceName, key, defaultValue)),
);
}
const source = localization.values[sourceName];
if (!source) {
return false;
}
localizeSync(resourceName: string, key: string, defaultValue: string): string {
const localization = this.store.selectSnapshot(ConfigState.getOne('localization'));
return createLocalizer(localization)(resourceName, key, defaultValue);
}
const value = source[key];
if (value === undefined) {
return false;
}
localizeWithFallback(
resourceNames: string[],
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 { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { Store, Actions } from '@ngxs/store';
import { Observable, of, Subject } from 'rxjs';
import { Actions, Store } from '@ngxs/store';
import { of, Subject } from 'rxjs';
import { LocalizationService } from '../services/localization.service';
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 './initial-utils';
export * from './lazy-load-utils';
export * from './localization-utils';
export * from './number-utils';
export * from './route-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()
color = '#77b6ff';
@Input()
filter = (action: StartLoader | StopLoader) =>
action.payload.url.indexOf('openid-configuration') < 0;
@Input()
isLoading = false;
@ -47,6 +43,10 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
stopDelay = 800;
@Input()
filter = (action: StartLoader | StopLoader) =>
action.payload.url.indexOf('openid-configuration') < 0;
private readonly clearProgress = () => {
this.progressLevel = 0;
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" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>MyCompanyName.MyProjectName</RootNamespace>
</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
{
[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 GetPrincipal()
@ -36,4 +40,4 @@ namespace MyCompanyName.MyProjectName.Security
return _principal;
}
}
}
}

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

Loading…
Cancel
Save