Browse Source

Merge remote-tracking branch 'abpframework/dev' into docs

pull/4006/head
liangshiwei 6 years ago
parent
commit
09f0bc296e
  1. 2
      .github/workflows/build-and-test.yml
  2. 4
      README.md
  3. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  4. 21
      docs/en/Dapper.md
  5. 47
      docs/en/Object-Extensions.md
  6. 3
      docs/en/Samples/Index.md
  7. 456
      docs/en/Text-Templating.md
  8. 12
      docs/en/UI/Angular/Confirmation-Service.md
  9. 165
      docs/en/UI/Angular/List-Service.md
  10. 6
      docs/en/UI/Angular/Track-By-Service.md
  11. 4
      docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md
  12. 8
      docs/en/docs-nav.json
  13. BIN
      docs/en/images/hello-template.png
  14. BIN
      docs/en/images/multiple-file-template.png
  15. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/EntityExtensionDto.cs
  16. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionEnumDto.cs
  17. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionEnumFieldDto.cs
  18. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyAttributeDto.cs
  19. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyDto.cs
  20. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ObjectExtensionsDto.cs
  21. 38
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperLocalizer.cs
  22. 21
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperExtensions.cs
  23. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs
  24. 56
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs
  25. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs
  26. 68
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs
  27. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/IAbpTagHelperLocalizer.cs
  28. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs
  29. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/PagerModel.cs
  30. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs
  31. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerStyleContributor.cs
  32. 16
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JQueryFormScriptContributor.cs
  33. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JsTreeOptions.cs
  34. 26
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JsTree/JsTreeStyleContributor.cs
  35. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Luxon/LuxonScriptContributor.cs
  36. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
  37. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs
  38. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js
  39. 30
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  40. 100
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js
  41. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs
  42. 150
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs
  43. 29
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
  44. 90
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
  45. 95
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyAttributeDtoFactory.cs
  46. 0
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ICachedObjectExtensionsDtoService.cs
  47. 9
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/IExtensionPropertyAttributeDtoFactory.cs
  48. 1
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertiesDictionaryModelBinderProvider.cs
  49. 6
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs
  50. 97
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/ObjectExtending/ObjectExtendingPropertyInfoExtensions.cs
  51. 58
      framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpHub.cs
  52. 19
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs
  53. 10
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs
  54. 46
      framework/src/Volo.Abp.Core/Volo/Abp/AbpInitializationException.cs
  55. 27
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs
  56. 22
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
  57. 97
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
  58. 29
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  59. 1
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  60. 47
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
  61. 0
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs
  62. 0
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizationResourceNameAttribute.cs
  63. 2
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
  64. 50
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StringLocalizerHelper.cs
  65. 9
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
  66. 37
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensionPropertyHelper.cs
  67. 38
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/IBasicObjectExtensionPropertyInfo.cs
  68. 20
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs
  69. 24
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfiguration.cs
  70. 32
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs
  71. 4
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs
  72. 25
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  73. 22
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/TypeHelper_Tests.cs
  74. 6
      framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json
  75. 132
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs
  76. 4
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hant.json
  77. 12
      modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs
  78. 8
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml
  79. 7
      modules/blogging/src/Volo.Blogging.Web/SocialMedia/BloggingTwitterOptions.cs
  80. 3
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserLookupAppService.cs
  81. 11
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UserLookupSearchInputDto.cs
  82. 17
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserLookupAppService.cs
  83. 30
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json
  84. 19
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExternalUserLookupServiceProvider.cs
  85. 21
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs
  86. 19
      modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientIdentityUserLookupService.cs
  87. 8
      modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserLookupController.cs
  88. 23
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs
  89. 35
      modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserLookupAppService_Tests.cs
  90. 9
      modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IExternalUserLookupServiceProvider.cs
  91. 7
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserLookupService.cs
  92. 8
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserRepository.cs
  93. 13
      modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupService.cs
  94. 20
      modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs
  95. 17
      modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs
  96. 3
      npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts
  97. 3
      npm/ng-packs/apps/dev-app/src/environments/environment.ts
  98. 28
      npm/ng-packs/package.json
  99. 2
      npm/ng-packs/packages/core/package.json
  100. 2
      npm/ng-packs/packages/core/src/lib/actions/session.actions.ts

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

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

4
README.md

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

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

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

21
docs/en/Dapper.md

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

47
docs/en/Object-Extensions.md

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

3
docs/en/Samples/Index.md

@ -45,6 +45,9 @@ While there is no Razor Pages & MongoDB combination, you can check both document
* [Source code](https://github.com/abpframework/abp-samples/tree/master/RabbitMqEventBus)
* [Distributed event bus document](../Distributed-Event-Bus.md)
* [RabbitMQ distributed event bus integration document](../Distributed-Event-Bus-RabbitMQ-Integration.md)
* **Text Templates Demo**: Shows different use cases of the text templating system.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo)
* [Text templating documentation](../Text-Templating.md)
* **Authentication Customization**: A solution to show how to customize the authentication for ASP.NET Core MVC / Razor Pages applications.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)
* Related "[How To](../How-To/Index.md)" documents:

456
docs/en/Text-Templating.md

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

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

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

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

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

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

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

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/tr-tr/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.

8
docs/en/docs-nav.json

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -54,8 +54,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
CurrentPage = currentPage;
}
ShowingFrom = totalCount == 0 ? 0 : CurrentPage * PageSize;
ShowingTo = totalCount == 0 ? 0 : ShowingFrom + PageSize;
ShowingFrom = totalCount == 0 ? 0 : (CurrentPage - 1) * PageSize + 1;
ShowingTo = totalCount == 0 ? 0 : (int)Math.Min(ShowingFrom + PageSize - 1 , totalCount);
PreviousPage = CurrentPage <= 1 ? 1 : CurrentPage - 1;
NextPage = CurrentPage >= TotalPageCount ? CurrentPage : CurrentPage + 1;
Pages = CalculatePageNumbers();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

28
npm/ng-packs/package.json

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

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

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

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

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

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

Loading…
Cancel
Save