Browse Source

Merge pull request #1 from abpframework/master

Merging from upstream
pull/816/head
Y2zz 7 years ago
committed by GitHub
parent
commit
70fd900c21
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 403
      docs/en/Samples/Microservice-Demo.md
  2. BIN
      docs/en/images/microservice-sample-authserver-home.png
  3. BIN
      docs/en/images/microservice-sample-authserver-login.png
  4. BIN
      docs/en/images/microservice-sample-backend-ui-permissions.png
  5. BIN
      docs/en/images/microservice-sample-backend-ui.png
  6. BIN
      docs/en/images/microservice-sample-public-product-list.png
  7. BIN
      docs/en/images/microservice-sample-solution.png
  8. 3
      docs/zh-Hans/Event-Bus.md
  9. 30
      docs/zh-Hans/Microservice-Architecture.md
  10. 40
      docs/zh-Hans/Samples/Microservice-Demo.md
  11. 17
      docs/zh-Hans/docs-nav.json
  12. BIN
      docs/zh-Hans/images/microservice-sample-diagram.png
  13. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo.Abp.AspNetCore.Mvc.Client.csproj
  14. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs
  15. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClient.cs
  16. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClientExtensions.cs
  17. 70
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs
  18. 33
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteSettingProvider.cs
  19. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs
  20. 11
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationSettingConfigurationDto.cs
  21. 88
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperService.cs
  22. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbItemTagHelperService.cs
  23. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Carousel/AbpCarouselItemTagHelperService.cs
  24. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs
  25. 27
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Dropdown/AbpDropdownButtonTagHelperService.cs
  26. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Dropdown/AbpDropdownTagHelperService.cs
  27. 20
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/ModelExplorerExtensions.cs
  28. 20
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperContextExtensions.cs
  29. 41
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperExtensions.cs
  30. 18
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperOutputExtensions.cs
  31. 19
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs
  32. 65
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs
  33. 20
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs
  34. 53
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs
  35. 44
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelperService.cs
  36. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpRowTagHelper.cs
  37. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpRowTagHelperService.cs
  38. 11
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Label/AbpLabelTagHelper.cs
  39. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Label/AbpLabelTagHelperService.cs
  40. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs
  41. 14
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs
  42. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/ProgressBar/AbpProgressGroupTagHelperService.cs
  43. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabDropdownTagHelperService.cs
  44. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs
  45. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabTagHelperService.cs
  46. 19
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  47. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs
  48. 37
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  49. 13
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs
  50. 1
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs
  51. 58
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AbpCorrelationIdMiddleware.cs
  52. 49
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs
  53. 6
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
  54. 6
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditLogInfo.cs
  55. 13
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs
  56. 1
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs
  57. 11
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs
  58. 35
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs
  59. 14
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs
  60. 31
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs
  61. 22
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
  62. 9
      framework/src/Volo.Abp.Core/Volo/Abp/Tracing/CorrelationIdOptions.cs
  63. 18
      framework/src/Volo.Abp.Core/Volo/Abp/Tracing/DefaultCorrelationIdProvider.cs
  64. 10
      framework/src/Volo.Abp.Core/Volo/Abp/Tracing/ICorrelationIdProvider.cs
  65. 1
      framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj
  66. 17
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs
  67. 2
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  68. 8
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs
  69. 101
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Values/ValueObject.cs
  70. 3
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs
  71. 10
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderConfiguration.cs
  72. 10
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Smtp/SmtpEmailSenderConfiguration.cs
  73. 26
      framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs
  74. 35
      framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/RemoteServiceConfigurationExtensions.cs
  75. 1
      framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj
  76. 9
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs
  77. 6
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Authentication/RemoteServiceHttpClientAuthenticateContext.cs
  78. 95
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
  79. 3
      framework/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpModule.cs
  80. 3
      framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs
  81. 2
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LanguageProviderExtensions.cs
  82. 1
      framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj
  83. 19
      framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs
  84. 45
      framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantSettingValueProvider.cs
  85. 44
      framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs
  86. 20
      framework/src/Volo.Abp.Security/Volo/Abp/Clients/CurrentClient.cs
  87. 9
      framework/src/Volo.Abp.Security/Volo/Abp/Clients/ICurrentClient.cs
  88. 5
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs
  89. 1
      framework/src/Volo.Abp.Settings/Volo.Abp.Settings.csproj
  90. 16
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs
  91. 14
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/DefaultValueSettingValueProvider.cs
  92. 12
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/GlobalSettingValueProvider.cs
  93. 2
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ISettingDefinitionManager.cs
  94. 13
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ISettingProvider.cs
  95. 15
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ISettingStore.cs
  96. 8
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ISettingValueProvider.cs
  97. 31
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs
  98. 31
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingManagerExtensions.cs
  99. 34
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingManagerSyncExtensions.cs
  100. 6
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingOptions.cs

403
docs/en/Samples/Microservice-Demo.md

@ -6,7 +6,7 @@
## Introduction
One of the major goals of the ABP framework is to provide a [convenient infrastructure to create microservice solutions](Microservice-Architecture.md).
One of the major goals of the ABP framework is to provide a [convenient infrastructure to create microservice solutions](../Microservice-Architecture.md).
This sample aims to demonstrate a simple yet complete microservice solution;
@ -19,7 +19,8 @@ This sample aims to demonstrate a simple yet complete microservice solution;
* Has a **console application** to show the simplest way of using a service by authenticating.
* Uses [Redis](https://redis.io/) for **distributed caching**.
* Uses [RabbitMQ](https://www.rabbitmq.com/) for service-to-service **messaging**.
* Uses [Kubernates](https://kubernetes.io/) to **deploy** & run all services and applications.
* Uses [Docker](https://www.docker.com/) & [Kubernates](https://kubernetes.io/) to **deploy** & run all services and applications.
* Uses [Elasticsearch](https://www.elastic.co/products/elasticsearch) & [Kibana](https://www.elastic.co/products/kibana) to store and visualize the logs (written using [Serilog](https://serilog.net/)).
The diagram below shows the system:
@ -33,8 +34,404 @@ You can get the source code from [the GitHub repository](https://github.com/abpf
This sample is still in development, not completed yet.
## Running the Solution
You can either run from the **source code** or from the pre-configured **docker-compose** file.
### Using the Docker Containers
#### Pre Requirements
Running as docker containers is easier since all dependencies are pre-configured. You only need to install the [latest docker](https://docs.docker.com/compose/install/).
#### Running Containers
- Clone or download the [ABP repository](https://github.com/abpframework/abp).
- Open a command line in the `samples/MicroserviceDemo` folder of the repository.
- Restore SQL Server databases:
```
docker-compose -f docker-compose.yml -f docker-compose.migrations.yml run restore-database
```
- Start the containers:
```
docker-compose up -d
```
At the first run, it will take a **long time** because it will build all docker images.
- Add this line to the end of your `hosts` file:
```
127.0.0.1 auth-server
```
hosts file is located inside the `C:\Windows\System32\Drivers\etc\hosts` folder on Windows and `/etc/hosts` for Linux/MacOS.
#### Run the Applications
There are a few applications running in the containers you may want to explore:
* Backend Admin Application (BackendAdminApp.Host): `http://localhost:51512`
*(Used to manage users & products in the system)*
* Public Web Site (PublicWebsite.Host): `http://localhost:51513`
*(Used to list products and run/manage the blog module)*
* Authentication Server (AuthServer.Host): `http://auth-server:51511/`
*(Used as a single sign on and authentication server built with IdentityServer4)*
* Kibana UI: `http://localhost:51510`
*(Use to show/trace logs written by all services/applications/gateways)*
### Running From the Source Code
#### Pre Requirements
To be able to run the solution from source code, following tools should be installed and running on your computer:
* [SQL Server](https://www.microsoft.com/en-us/sql-server/sql-server-downloads) 2015+ (can be [express edition](https://www.microsoft.com/en-us/sql-server/sql-server-editions-express))
* [Redis](https://redis.io/download) 5.0+
* [RabbitMQ](https://www.rabbitmq.com/install-windows.html) 3.7.11+
* [MongoDB](https://www.mongodb.com/download-center) 4.0+
* [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+
* [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (optional, recommended to show logs)
#### Open & Build the Visual Studio Solution
* Open the `samples\MicroserviceDemo\MicroserviceDemo.sln` in Visual Studio 2017 (15.9.0+).
* Run `dotnet restore` from the command line inside the `samples\MicroserviceDemo` folder.
* Build the solution in Visual Studio.
#### Restore Databases
Open `MsDemo_Identity.zip` and `MsDemo_ProductManagement.zip` inside the `samples\MicroserviceDemo\databases` folder and restore to the SQL Server.
> Notice that: These databases have EF Core migrations in the solution, however they don't have seed data, especially required for IdentityServer4 configuration. So, restoring the databases is much more easier.
#### Run Projects
Run the projects with the following order (right click to each project, set as startup project an press Ctrl+F5 to run without debug):
* AuthServer.Host
* IdentityService.Host
* BloggingService.Host
* ProductService.Host
* InternalGateway.Host
* BackendAdminAppGateway.Host
* PublicWebSiteGateway.Host
* BackendAdminApp.Host
* PublicWebSite.Host
## A Brief Overview of the Solution
The Visual Studio solution consists of multiple projects each have different roles in the system:
![microservice-sample-solution](../images/microservice-sample-solution.png)
### Applications
These are the actual applications those have user interfaces to interact to the users and use the system.
- **AuthServer.Host**: Host the IdentityServer4 to provide an authentication service to other services and applications. It is a single-sign server and contains the login page.
- **BackendAdminApp.Host**: This is a backend admin application that host UI for Identity and Product management modules.
- **PubicWebSite.Host**: As public web site that contains a simple product list page and blog module UI.
- **ConsoleClientDemo**: A simple console application to demonstrate the usage of services from a C# application.
### Gateways / BFFs (Backend for Frontend)
Gateways are used to provide a single entry point to the applications. It can also used for rate limiting, load balancing... etc. Used the [Ocelot](https://github.com/ThreeMammals/Ocelot) library.
* **BackendAdminAppGateway.Host**: Used by the BackendAdminApp.Host application as backend.
* **PublicWebSiteGateway.Host**: Used by the PublicWebSite.Host application as backend.
* **InternalGateway.Host**: Used for inter-service communication (the communication between microservices).
### Microservices
Microservices have no UI, but exposes some REST APIs.
- **IdentityService.Host**: Host the ABP Identity module which is used to manage users & roles. It has no additional service, but only hosts the Identity module's API.
- **BloggingService.Host**: Host the ABP Blogging module which is used to manage blog & posts (a typical blog application). It has no additional service, but only hosts the Blogging module's API.
- **ProductService.Host**: Hosts the Product module (that is inside the solution) which is used to manage products. It also contains the EF Core migrations to create/update the Product Management database schema.
### Modules
* **Product**: A layered module that is developed with the [module development best practices](../Best-Practices/Index.md). It can be embedded into a monolithic application or can be hosted as a microservice by separately deploying API and UI (as done in this demo solution).
### Databases
This solution is using multiple databases:
* **MsDemo_Identity**: An SQL database. Used **SQL Server** by default, but can be any DBMS supported by the EF Core. Shared by AuthServer and IdentityService. Also audit logs, permissions and settings are stored in this database (while they could easily have their own databases, shared the same database to keep it simple).
* **MsDemo_ProductManagement**: An SQL database. Again, used **SQL Server** by default, but can be any DBMS supported by the EF Core. Used by the ProductService as a dedicated database.
* **MsDemo_Blogging**: A **MongoDB** database. Used by the BloggingService.
* **Elasticsearch**: Used to write logs over Serilog.
## Applications
### Authentication Server (AuthServer.Host)
This project is used by all other services and applications for authentication & single sign on. Mainly, uses **IdentityServer4** to provide these services. It uses some of the [pre-build ABP modules](../Modules/Index) like *Identity*, *Audit Logging* and *Permission Management*.
#### Database & EF Core Configuration
This application uses a SQL database (named it as **MsDemo_Identity**) and maintains its schema via **Entity Framework Core migrations.**
It has a DbContext named **AuthServerDbContext** and defined as shown below:
````csharp
public class AuthServerDbContext : AbpDbContext<AuthServerDbContext>
{
public AuthServerDbContext(DbContextOptions<AuthServerDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigureIdentity();
modelBuilder.ConfigureIdentityServer();
modelBuilder.ConfigureAuditLogging();
modelBuilder.ConfigurePermissionManagement();
modelBuilder.ConfigureSettingManagement();
}
}
````
In the **OnModelCreating**, you see **ConfigureX()** method calls. A module with a database schema generally declares such an extension method to configure EF Core mappings for its own entities. This is a flexible approach where you can arrange your databases and modules inside them; You can use a different database for each module, or combine some of them in a shared database. In the AuthServer project, we decided to combine multiple module schemas in a single EF Core DbContext, in a single physical database. These modules are Identity, IdentityServer, AuditLogging, PermissionManagement and SettingManagement modules.
#### User Interface
AuthServer has a simple home page that shows the current user info if the current user has logged in:
![microservice-sample-authserver-home](../images/microservice-sample-authserver-home.png)
It also provides Login & Register pages:
![microservice-sample-authserver-login](../images/microservice-sample-authserver-login.png)
These pages are not included in the project itself. Instead, AuthServer project uses the prebuilt ABP [account module](https://github.com/abpframework/abp/tree/master/modules/account) with IdentityServer extension. That means it can also act as an OpenId Connect server with necessary UI and logic.
#### Other Dependencies
* **RabbitMQ** for messaging to other services.
* **Redis** for distributed/shared caching.
* **Elasticsearch** for storing logs.
### Backend Admin Application (BackendAdminApp.Host)
This is a web application that is used to manage users, roles, permissions and products in the system.
#### Authentication
BackendAdminApp redirects to the AuthServer for authentication. Once the user enters a correct username & password, the page is redirected to the backend application again. Authentication configuration is setup in the `BackendAdminAppHostModule` class:
````charp
context.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.Cookie.Expiration = TimeSpan.FromDays(365);
options.ExpireTimeSpan = TimeSpan.FromDays(365);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.ClientId = configuration["AuthServer:ClientId"];
options.ClientSecret = configuration["AuthServer:ClientSecret"];
options.RequireHttpsMetadata = false;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("role");
options.Scope.Add("email");
options.Scope.Add("phone");
options.Scope.Add("BackendAdminAppGateway");
options.Scope.Add("IdentityService");
options.Scope.Add("ProductService");
options.ClaimActions.MapAbpClaimTypes();
});
````
* It adds "Cookies" authentication as the primary authentication type.
* "oidc" authentication is configured to use the AuthServer application as the authentication server.
* It requires the additional identity scopes *role*, *email* and *phone*.
* It requires the API resource scopes *BackendAdminAppGateway*, *IdentityService* and *ProductService* because it will use these services as APIs.
IdentityServer client settings are stored inside the `appsettings.json` file:
````json
"AuthServer": {
"Authority": "http://localhost:64999",
"ClientId": "backend-admin-app-client",
"ClientSecret": "1q2w3e*"
}
````
#### User Interface
The BackendAdminApp.Host project itself has not a single UI element/page. It is only used to serve UI pages of the Identity and Product Management modules. `BackendAdminAppHostModule` adds dependencies to `AbpIdentityWebModule` (*[Volo.Abp.Identity.Web](https://www.nuget.org/packages/Volo.Abp.Identity.Web)* package) and `ProductManagementWebModule` (*ProductManagement.Web* project) for that purpose.
A screenshot from the user management page:
![microservice-sample-backend-ui](../images/microservice-sample-backend-ui.png)
A screenshot from the permission management modal for a role:
![microservice-sample-backend-ui-permissions](../images/microservice-sample-backend-ui-permissions.png)
#### Using Microservices
Backend admin application uses the Identity and Product microservices for all operations, over the Backend Admin Gateway (BackendAdminAppGateway.Host).
##### Remote End Point
`appsettings.json` file contains the `RemoteServices` section to declare the remote service endpoint(s). Each microservice will normally have different endpoints. However, this solution uses the API Gateway pattern to provide a single endpoint for the applications:
````json
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:65115/"
}
}
````
`http://localhost:65115/` is the URL of the *BackendAdminAppGateway.Host* project. It knows where are Identity and Product services are located.
##### HTTP Clients
ABP application modules generally provides C# client libraries to consume services (APIs) easily (they generally uses the [Dynamic C# API Clients](../AspNetCore/Dynamic-CSharp-API-Clients.md) feature of the ABP framework). That means if you need to consume Identity service API, you can reference to its client package and easily use the APIs by provided interfaces.
For that purpose, `BackendAdminAppHostModule` class declares dependencies for `AbpIdentityHttpApiClientModule` and `ProductManagementHttpApiClientModule`.
Once you refer these client packages, you can directly inject an application service interface (e.g. `IIdentityUserAppService`) and use its methods like a local method call. It actually invokes remote service calls over HTTP to the related service endpoint.
##### Passing the Access Token
Since microservices requires authentication & authorization, each remote service call should contain an Authentication header. This header is obtained from the `access_token` inside the current `HttpContext` for the current user. This is automatically done when you use the `Volo.Abp.Http.Client.IdentityModel` package. `BackendAdminAppHostModule` declares dependencies to this package and to the related `AbpHttpClientIdentityModelModule` class. It is integrated to the HTTP Clients explained above.
#### Other Dependencies
- **Redis** for distributed/shared caching.
- **Elasticsearch** for storing logs.
### Public Web Site (PublicWebSite.Host)
This is a public web site project that has a web blog and product list page.
#### Authentication
PublicWebSite can show blog posts and product list without login. If you login, you can also manage blogs. It redirects to the AuthServer for authentication. Once the user enters a correct username & password, the page is redirected to the public web site application again. Authentication configuration is setup in the `PublicWebSiteHostModule` class:
```charp
context.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.Cookie.Expiration = TimeSpan.FromDays(365);
options.ExpireTimeSpan = TimeSpan.FromDays(365);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.ClientId = configuration["AuthServer:ClientId"];
options.ClientSecret = configuration["AuthServer:ClientSecret"];
options.RequireHttpsMetadata = false;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("role");
options.Scope.Add("email");
options.Scope.Add("phone");
options.Scope.Add("PublicWebSiteGateway");
options.Scope.Add("ProductService");
options.Scope.Add("BloggingService");
options.ClaimActions.MapAbpClaimTypes();
});
```
- It adds "Cookies" authentication as the primary authentication type.
- "oidc" authentication is configured to use the AuthServer application as the authentication server.
- It requires the additional identity scopes *role*, *email* and *phone*.
- It requires the API resource scopes *PublicWebSiteGateway*, *BloggingService* and *ProductService* because it will use these services as APIs.
IdentityServer client settings are stored inside the `appsettings.json` file:
```json
"AuthServer": {
"Authority": "http://localhost:64999",
"ClientId": "public-website-client",
"ClientSecret": "1q2w3e*"
}
```
#### User Interface
The PublicWebSite.Host project has a page to list products (`Pages/Products.cshtml`). It also uses the UI from the blogging module. `PublicWebSiteHostModule` adds dependencies to `BloggingWebModule` (*[Volo.Blogging.Web](https://www.nuget.org/packages/Volo.Blogging.Web)* package) for that purpose.
A screenshot from the Products page:
![microservice-sample-public-product-list](../images/microservice-sample-public-product-list.png)
#### Using Microservices
Publc web site application uses the Blogging and Product microservices for all operations, over the Public Web Site Gateway (PublicWebSiteGateway.Host).
##### Remote End Point
`appsettings.json` file contains the `RemoteServices` section to declare the remote service endpoint(s). Each microservice will normally have different endpoints. However, this solution uses the API Gateway pattern to provide a single endpoint for the applications:
```json
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:64897/"
}
}
```
`http://localhost:64897/` is the URL of the *PublicWebSiteGateway.Host* project. It knows where are Blogging and Product services are located.
##### HTTP Clients
`PublicWebSiteHostModule` class declares dependencies for `BloggingHttpApiClientModule` and `ProductManagementHttpApiClientModule` to be able to use remote HTTP APIs for these services.
##### Passing the Access Token
Just like explained in the Backend Admin Application section, Public Web Site project also uses the `AbpHttpClientIdentityModelModule` to pass `access_token` to the calling services for authentication.
#### Other Dependencies
- **Redis** for distributed/shared caching.
- **Elasticsearch** for storing logs.
### Console Client Demo
TODO
## Microservices
### Identity Service
...
TODO
## Infrastructure
TODO
### Messaging
### Caching
### Logging
### Correlation Id

BIN
docs/en/images/microservice-sample-authserver-home.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/images/microservice-sample-authserver-login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/en/images/microservice-sample-backend-ui-permissions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/images/microservice-sample-backend-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
docs/en/images/microservice-sample-public-product-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/images/microservice-sample-solution.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

3
docs/zh-Hans/Event-Bus.md

@ -0,0 +1,3 @@
# Event Bus
TODO

30
docs/zh-Hans/Microservice-Architecture.md

@ -0,0 +1,30 @@
# 微服务架构
*"作为**面向服务架构**(SOA)的一个变体,微服务是一种将应用程序分解成**松散耦合服务**的新型架构风格. 通过**细粒度**的服务和**轻量级**的协议,微服务提供了更多的**模块化**,使应用程序更容易理解,开发,测试,并且更容易抵抗架构侵蚀. 它使小型团队能够**开发,部署和扩展**各自的服务,实现开发的**并行化**.它还允许通过**连续重构**形成单个服务的架构. 基于微服务架构可以实现**持续交付和部署**."*
— [维基百科](https://zh.wikipedia.org/wiki/Microservices)
## 介绍
ABP框架的主要目标之一就是提供**便捷的基础设施来创建微服务解决方案**. 我们做了以下工作:
* 提供[模块系统](Module-Development-Basics.md),允许将应用程序拆分为模块,其中每个模块可以拥有自己的数据库,实体,服务,API,UI组件/页面....等.
* 提供[架构模型](Best-Practices/Module-Architecture.md)来开发模块,与微服务开发和部署兼容.
* 提供[最佳实践指南](Best-Practices/Index.md)制定模块开发标准.
* 提供基础设施来实现微服务中的[领域驱动设计](Domain-Driven-Design.md).
* 提供从应用程序服务[自动生成REST风格的API](AspNetCore/Auto-API-Controllers.md)的服务.
* 提供[自动创建C#API客户端](AspNetCore/Dynamic-CSharp-API-Clients.md)服务,以便从其他服务/应用程序使用你服务.
* 提供[分布式事件总线](Event-Bus.md)用于服务通信.
* 提供更多其他服务,使日常开发更加简便.
## 在新应用程序中使用微服务
开始一个新解决方案建议**始终从单体开始**, 保持模块化,在单体成为问题时将其拆分为微服务.这使初期进度会很快,特别是如果你的团队人数不多,并且不想处理微服务架构带来的各种挑战.
然而开发一个良好的模块化应用程序不是那么简单,因为很难像微服务那样**保持模块之间的隔离** (参阅 [Stefan Tilkov的文章](https://martinfowler.com/articles/dont-start-monolith.html)). 微服务架构会自然的让你开发隔离的服务,但是在模块化的单体应用程序中,模块很容易彼此紧密耦合并设计出**弱模块边界**和API约定.
ABP可以帮助你,它提供了与**与微服务兼容的严格模块架构** 在这个架构中你的模块被分割成多个层/项目,在自己的VS解决方案中进行开发,该解决方案完成独立于其它模块. 这种方式开发的模块是一种天然的微服务,但是它可以很容易的插入到单体应用程序中. 请参阅**微服务优先的模块设计**的[模块开发最佳实践指南](Best-Practices/Index.md). 所有[标准的ABP模块](https://github.com/abpframework/abp/tree/master/modules)都是基于本指南开发的. 因此你可以将这些模块嵌入到单体解决方案中使用它们,也可以单独部署通过远程API调用. 它们可以共享一个数据库,也可以通过简单配置使用自己的数据库.
## 微服务解决方案示例
[微服务解决方案示例](Samples/Microservice-Demo.md)演示了基于ABP框架的完整的微服务的解决方案.

40
docs/zh-Hans/Samples/Microservice-Demo.md

@ -0,0 +1,40 @@
# 微服务解决方案示例
*"作为**面向服务架构**(SOA)的一个变体,微服务是一种将应用程序分解成**松散耦合服务**的新型架构风格. 通过**细粒度**的服务和**轻量级**的协议,微服务提供了更多的**模块化**,使应用程序更容易理解,开发,测试,并且更容易抵抗架构侵蚀. 它使小型团队能够**开发,部署和扩展**各自的服务,实现开发的**并行化**.它还允许通过**连续重构**形成单个服务的架构. 基于微服务架构可以实现**持续交付和部署**."*
— [维基百科](https://zh.wikipedia.org/wiki/Microservices)
## 介绍
ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服务解决方案](../Microservice-Architecture.md).
此示例演示了一个简单而完整的微服务解决方案;
* 拥有多个可独立可单独部署的**微服务**.
* 多个**Web应用程序**, 每一个都使用不同的API网关.
* 使用[Ocelot](https://github.com/ThreeMammals/Ocelot)库开发了多个**网关** / BFFs ([用于前端的后端](https://docs.microsoft.com/zh-cn/azure/architecture/patterns/backends-for-frontends)).
* 包含使用[IdentityServer](https://identityserver.io/)框架开发的 **身份认证服务**. 它也是一个带有UI的SSO(单点登陆)应用程序.
* 有**多个数据库**. 一些微服务有自己的数据库,也有一些服务/应用程序共享同一个数据库(以演示不同的用例).
* 有不同类型的数据库: **SQL Server** (与 **Entity Framework Core** ORM) 和 **MongoDB**.
* 有一个**控制台应用程序**使用身份验证展示使用服务最简单的方法.
* 使用[Redis](https://redis.io/)做**分布式缓存**.
* 使用[RabbitMQ](https://www.rabbitmq.com/)做服务间的**消息**传递.
* 使用[Kubernates](https://kubernetes.io/)**部署**和运行所有的服务和应用程序.
下图显示了该系统:
![microservice-sample-diagram](../images/microservice-sample-diagram.png)
### 源码
你可以从[GitHub仓库](https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo)获取源码.
### 状态
此示例仍处于开发阶段,尚未完成.
## 微服务
### 身份认证服务
...

17
docs/zh-Hans/docs-nav.json

@ -255,6 +255,23 @@
}
]
},
{
"text": "示例",
"items": [
{
"text": "微服务示例",
"path": "Samples/Microservice-Demo.md"
}
]
},
{
"text": "应用模块",
"path": "Modules/Index.md"
},
{
"text": "微服务架构",
"path": "Microservice-Architecture.md"
},
{
"text": "测试"
},

BIN
docs/zh-Hans/images/microservice-sample-diagram.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

1
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo.Abp.AspNetCore.Mvc.Client.csproj

@ -17,6 +17,7 @@
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj" />
<ProjectReference Include="..\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
<ProjectReference Include="..\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
</ItemGroup>
</Project>

9
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs

@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Caching;
using Volo.Abp.Http.Client;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Mvc.Client
@ -8,7 +9,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Client
[DependsOn(
typeof(AbpHttpClientModule),
typeof(AbpAspNetCoreMvcContractsModule),
typeof(AbpCachingModule)
typeof(AbpCachingModule),
typeof(AbpLocalizationModule)
)]
public class AbpAspNetCoreMvcClientModule : AbpModule
{
@ -21,6 +23,11 @@ namespace Volo.Abp.AspNetCore.Mvc.Client
RemoteServiceName,
asDefaultServices: false
);
Configure<AbpLocalizationOptions>(options =>
{
options.GlobalContributors.Add<RemoteLocalizationContributor>();
});
}
}
}

5
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClient.cs

@ -1,4 +1,5 @@
using System;
using System.Globalization;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
@ -45,7 +46,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Client
async () => await Proxy.Service.GetAsync(),
() => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5)
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(60) //TODO: Should be configurable. Default value should be higher (5 mins would be good).
}
);
@ -59,7 +60,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Client
protected virtual string CreateCacheKey()
{
return $"ApplicationConfiguration_{CurrentUser.Id?.ToString("N") ?? "Anonymous"}";
return $"ApplicationConfiguration_{CurrentUser.Id?.ToString("N") ?? "Anonymous"}_{CultureInfo.CurrentUICulture.Name}";
}
}
}

13
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClientExtensions.cs

@ -0,0 +1,13 @@
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Threading;
namespace Volo.Abp.AspNetCore.Mvc.Client
{
public static class CachedApplicationConfigurationClientExtensions
{
public static ApplicationConfigurationDto Get(this ICachedApplicationConfigurationClient client)
{
return AsyncHelper.RunSync(client.GetAsync);
}
}
}

70
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs

@ -0,0 +1,70 @@
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Localization;
namespace Volo.Abp.AspNetCore.Mvc.Client
{
public class RemoteLocalizationContributor : ILocalizationResourceContributor
{
private LocalizationResource _resource;
private ICachedApplicationConfigurationClient _applicationConfigurationClient;
private ILogger<RemoteLocalizationContributor> _logger;
public void Initialize(LocalizationResourceInitializationContext context)
{
_resource = context.Resource;
_applicationConfigurationClient = context.ServiceProvider.GetRequiredService<ICachedApplicationConfigurationClient>();
_logger = context.ServiceProvider.GetService<ILogger<RemoteLocalizationContributor>>()
?? NullLogger<RemoteLocalizationContributor>.Instance;
}
public LocalizedString GetOrNull(string cultureName, string name)
{
var resource = GetResourceOrNull();
if (resource == null)
{
return null;
}
var value = resource.GetOrDefault(name);
if (value == null)
{
return null;
}
return new LocalizedString(name, value);
}
public void Fill(string cultureName, Dictionary<string, LocalizedString> dictionary)
{
var resource = GetResourceOrNull();
if (resource == null)
{
return;
}
foreach (var keyValue in resource)
{
dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value);
}
}
private Dictionary<string, string> GetResourceOrNull()
{
var resource = _applicationConfigurationClient
.Get()
.Localization.Values
.GetOrDefault(_resource.ResourceName);
if (resource == null)
{
_logger.LogWarning($"Could not find the localization resource {_resource.ResourceName} on the remote server!");
}
return resource;
}
}
}

33
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteSettingProvider.cs

@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Settings;
namespace Volo.Abp.AspNetCore.Mvc.Client
{
public class RemoteSettingProvider : ISettingProvider, ITransientDependency
{
protected ICachedApplicationConfigurationClient ConfigurationClient { get; }
public RemoteSettingProvider(ICachedApplicationConfigurationClient configurationClient)
{
ConfigurationClient = configurationClient;
}
public async Task<string> GetOrNullAsync(string name)
{
var configuration = await ConfigurationClient.GetAsync();
return configuration.Setting.Values.GetOrDefault(name);
}
public async Task<List<SettingValue>> GetAllAsync()
{
var configuration = await ConfigurationClient.GetAsync();
return configuration
.Setting.Values
.Select(s => new SettingValue(s.Key, s.Value))
.ToList();
}
}
}

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

@ -9,6 +9,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
public ApplicationAuthConfigurationDto Auth { get; set; }
public ApplicationSettingConfigurationDto Setting { get; set; }
public CurrentUserDto CurrentUser { get; set; }
}
}

11
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationSettingConfigurationDto.cs

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
[Serializable]
public class ApplicationSettingConfigurationDto
{
public Dictionary<string, string> Values { get; set; }
}
}

88
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperService.cs

@ -56,93 +56,5 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers
Process(context, output);
return Task.CompletedTask;
}
protected virtual TagHelperOutput GetInnerTagHelper(TagHelperAttributeList attributeList, TagHelperContext context, TagHelper tagHelper, string tagName = "div", TagMode tagMode = TagMode.SelfClosing, bool runAsync = false)
{
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());
tagHelper.Init(context);
if (runAsync)
{
AsyncHelper.RunSync(() => tagHelper.ProcessAsync(innerContext, innerOutput));
}
else
{
tagHelper.Process(innerContext, innerOutput);
}
return innerOutput;
}
protected virtual string RenderTagHelper(TagHelperAttributeList attributeList, TagHelperContext context, TagHelper tagHelper, HtmlEncoder htmlEncoder, string tagName = "div", TagMode tagMode = TagMode.SelfClosing, bool runAsync = false)
{
var innerOutput = GetInnerTagHelper(attributeList, context, tagHelper, tagName, tagMode, runAsync);
return RenderTagHelperOutput(innerOutput, htmlEncoder);
}
protected virtual string RenderTagHelperOutput(TagHelperOutput output, HtmlEncoder htmlEncoder)
{
using (var writer = new StringWriter())
{
output.WriteTo(writer, htmlEncoder);
return writer.ToString();
}
}
protected virtual T GetAttribute<T>(ModelExplorer property) where T : Attribute
{
return property?.Metadata?.ContainerType?.GetTypeInfo()?.GetProperty(property.Metadata.PropertyName)?.GetCustomAttribute<T>();
}
protected virtual List<FormGroupItem> GetFormGroupContentsList(TagHelperContext context, out bool surpress)
{
var items = GetValueFromContext<List<FormGroupItem>>(context, FormGroupContents);
surpress = items != null;
return items ?? new List<FormGroupItem>();
}
protected virtual T GetValueFromContext<T>(TagHelperContext context, string key)
{
if (!context.Items.ContainsKey(key))
{
return default(T);
}
return (T)context.Items[key];
}
protected virtual string GetIdAttributeAsString(TagHelperOutput inputTag)
{
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
return idAttr != null ? "for=\"" + idAttr.Value + "\"" : "";
}
protected virtual int GetInputOrder(ModelExplorer explorer)
{
return GetAttribute<DisplayOrder>(explorer)?.Number ?? DisplayOrder.Default;
}
protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool surpress)
{
var list = GetFormGroupContentsList(context, out surpress);
if (list != null && !list.Any(igc => igc.HtmlContent.Contains("id=\"" + propertyName.Replace('.', '_') + "\"")))
{
list.Add(new FormGroupItem
{
HtmlContent = html,
Order = order
});
}
}
}
}

5
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbItemTagHelperService.cs

@ -2,6 +2,7 @@
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb
{
@ -20,13 +21,13 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb
output.Attributes.AddClass("breadcrumb-item");
output.Attributes.AddClass(AbpBreadcrumbItemActivePlaceholder);
var list = GetValueFromContext<List<BreadcrumbItem>>(context, BreadcrumbItemsContent);
var list = context.GetValue<List<BreadcrumbItem>>(BreadcrumbItemsContent);
output.Content.SetHtmlContent(GetInnerHtml(context, output));
list.Add(new BreadcrumbItem
{
Html = RenderTagHelperOutput(output, _encoder),
Html = output.Render(_encoder),
Active = TagHelper.Active
});

5
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Carousel/AbpCarouselItemTagHelperService.cs

@ -3,6 +3,7 @@ using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
{
@ -32,9 +33,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
private void AddToContext(TagHelperContext context, TagHelperOutput output)
{
var getOutputAsHtml = RenderTagHelperOutput(output, _encoder);
var getOutputAsHtml = output.Render(_encoder);
var itemList = GetValueFromContext<List<CarouselItem>>(context, CarouselItemsContent);
var itemList = context.GetValue<List<CarouselItem>>(CarouselItemsContent);
itemList.Add(new CarouselItem(getOutputAsHtml, TagHelper.Active ?? false));
}

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse
{
@ -15,7 +16,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse
var html = GetAccordionHeaderItem(context, output) + GetAccordionContentItem(context, output, innerContent);
var tabHeaderItems = GetValueFromContext<List<string>>(context, AccordionItems);
var tabHeaderItems = context.GetValue<List<string>>(AccordionItems);
tabHeaderItems.Add(html);

27
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Dropdown/AbpDropdownButtonTagHelperService.cs

@ -1,10 +1,12 @@
using System;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Dropdown
{
@ -22,22 +24,25 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Dropdown
_serviceProvider = serviceProvider;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var buttonsAsHtml = GetButtonsAsHtml(context, output);
var content = await output.GetChildContentAsync();
var buttonsAsHtml = GetButtonsAsHtml(context, output, content);
output.PreElement.SetHtmlContent(buttonsAsHtml);
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
output.Content.SetContent("");
output.Attributes.Clear();
}
protected virtual string GetButtonsAsHtml(TagHelperContext context, TagHelperOutput output)
protected virtual string GetButtonsAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperContent content)
{
var buttonBuilder = new StringBuilder("");
var mainButton = GetMainButton(context, output);
var mainButton = GetMainButton(context, output, content);
buttonBuilder.AppendLine(mainButton);
@ -51,10 +56,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Dropdown
return buttonBuilder.ToString();
}
protected virtual string GetMainButton(TagHelperContext context, TagHelperOutput output)
protected virtual string GetMainButton(TagHelperContext context, TagHelperOutput output, TagHelperContent content)
{
var abpButtonTagHelper = _serviceProvider.GetRequiredService<AbpButtonTagHelper>();
abpButtonTagHelper.Icon = TagHelper.Icon;
abpButtonTagHelper.Text = TagHelper.Text;
abpButtonTagHelper.IconType = TagHelper.IconType;
@ -62,15 +67,17 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Dropdown
abpButtonTagHelper.ButtonType = TagHelper.ButtonType;
var attributes = GetAttributesForMainButton(context, output);
var buttonTag = GetInnerTagHelper(attributes, context, abpButtonTagHelper, "button", TagMode.StartTagAndEndTag);
var buttonTag = abpButtonTagHelper.ProcessAndGetOutput(attributes, context, "button", TagMode.StartTagAndEndTag);
buttonTag.PreContent.SetHtmlContent(content.GetContent());
if ((TagHelper.NavLink ?? false) || (TagHelper.Link ?? false))
{
var linkTag = ConvertButtonToLink(buttonTag);
return RenderTagHelperOutput(linkTag, _htmlEncoder);
return linkTag.Render(_htmlEncoder);
}
return RenderTagHelperOutput(buttonTag, _htmlEncoder);
return buttonTag.Render(_htmlEncoder);
}
protected virtual string GetSplitButton(TagHelperContext context, TagHelperOutput output)
@ -81,7 +88,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Dropdown
abpButtonTagHelper.ButtonType = TagHelper.ButtonType;
var attributes = GetAttributesForSplitButton(context, output);
return RenderTagHelper(attributes, context, abpButtonTagHelper, _htmlEncoder, "button", TagMode.StartTagAndEndTag);
return abpButtonTagHelper.Render(attributes, context, _htmlEncoder, "button", TagMode.StartTagAndEndTag);
}
protected virtual TagHelperAttributeList GetAttributesForMainButton(TagHelperContext context, TagHelperOutput output)

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Dropdown/AbpDropdownTagHelperService.cs

@ -8,6 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Dropdown
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.AddClass("dropdown");
output.Attributes.AddClass("btn-group");
SetDirection(context, output);

20
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/ModelExplorerExtensions.cs

@ -0,0 +1,20 @@
using System;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions
{
public static class ModelExplorerExtensions
{
public static T GetAttribute<T>(this ModelExplorer property) where T : Attribute
{
return property?.Metadata?.ContainerType?.GetTypeInfo()?.GetProperty(property.Metadata.PropertyName)?.GetCustomAttribute<T>();
}
public static int GetDisplayOrder(this ModelExplorer explorer)
{
return GetAttribute<DisplayOrder>(explorer)?.Number ?? DisplayOrder.Default;
}
}
}

20
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperContextExtensions.cs

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions
{
public static class TagHelperContextExtensions
{
public static T GetValue<T>(this TagHelperContext context, string key)
{
if (!context.Items.ContainsKey(key))
{
return default(T);
}
return (T)context.Items[key];
}
}
}

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

@ -0,0 +1,41 @@
using System;
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 TagHelperOutput ProcessAndGetOutput(this TagHelper tagHelper, TagHelperAttributeList attributeList, TagHelperContext context, string tagName = "div", TagMode tagMode = TagMode.SelfClosing, bool runAsync = false)
{
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());
tagHelper.Init(context);
if (runAsync)
{
AsyncHelper.RunSync(() => tagHelper.ProcessAsync(innerContext, innerOutput));
}
else
{
tagHelper.Process(innerContext, innerOutput);
}
return innerOutput;
}
public static string Render(this TagHelper tagHelper, TagHelperAttributeList attributeList, TagHelperContext context, HtmlEncoder htmlEncoder, string tagName = "div", TagMode tagMode = TagMode.SelfClosing, bool runAsync = false)
{
var innerOutput = tagHelper.ProcessAndGetOutput(attributeList, context, tagName, tagMode, runAsync);
return innerOutput.Render(htmlEncoder);
}
}
}

18
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperOutputExtensions.cs

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.IO;
using System.Text.Encodings.Web;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions
{
public static class TagHelperOutputExtensions
{
public static string Render(this TagHelperOutput output, HtmlEncoder htmlEncoder)
{
using (var writer = new StringWriter())
{
output.WriteTo(writer, htmlEncoder);
return writer.ToString();
}
}
}
}

19
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs

@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
@ -66,7 +67,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
ViewContext = TagHelper.ViewContext
};
var formTagOutput = GetInnerTagHelper(output.Attributes, context, formTagHelper, "form", TagMode.StartTagAndEndTag);
var formTagOutput = formTagHelper.ProcessAndGetOutput(output.Attributes, context, "form", TagMode.StartTagAndEndTag);
await formTagOutput.GetChildContentAsync();
@ -146,7 +147,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
var abpSelectTagHelper = GetSelectGroupTagHelper(context, output, model);
RenderTagHelper(new TagHelperAttributeList(), context, abpSelectTagHelper, _htmlEncoder, "div", TagMode.StartTagAndEndTag);
abpSelectTagHelper.Render(new TagHelperAttributeList(), context, _htmlEncoder, "div", TagMode.StartTagAndEndTag);
}
protected virtual AbpTagHelper GetSelectGroupTagHelper(TagHelperContext context, TagHelperOutput output, ModelExpression model)
@ -156,7 +157,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
GetSelectTagHelper(model);
}
private AbpTagHelper GetSelectTagHelper(ModelExpression model)
protected virtual AbpTagHelper GetSelectTagHelper(ModelExpression model)
{
var abpSelectTagHelper = _serviceProvider.GetRequiredService<AbpSelectTagHelper>();
abpSelectTagHelper.AspFor = model;
@ -165,9 +166,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return abpSelectTagHelper;
}
private AbpTagHelper GetAbpRadioInputTagHelper(ModelExpression model)
protected virtual AbpTagHelper GetAbpRadioInputTagHelper(ModelExpression model)
{
var radioButtonAttribute = GetAttribute<AbpRadioButton>(model.ModelExplorer);
var radioButtonAttribute = model.ModelExplorer.GetAttribute<AbpRadioButton>();
var abpRadioInputTagHelper = _serviceProvider.GetRequiredService<AbpRadioInputTagHelper>();
abpRadioInputTagHelper.AspFor = model;
abpRadioInputTagHelper.AspItems = null;
@ -184,7 +185,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
abpButtonTagHelper.Text = "Submit";
abpButtonTagHelper.ButtonType = AbpButtonType.Primary;
return RenderTagHelper(attributes, context, abpButtonTagHelper, _htmlEncoder, "button", TagMode.StartTagAndEndTag);
return abpButtonTagHelper.Render(attributes, context, _htmlEncoder, "button", TagMode.StartTagAndEndTag);
}
protected virtual void ProcessInputGroup(TagHelperContext context, TagHelperOutput output, ModelExpression model)
@ -194,7 +195,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
abpInputTagHelper.ViewContext = TagHelper.ViewContext;
abpInputTagHelper.DisplayRequiredSymbol = TagHelper.RequiredSymbols ?? true;
RenderTagHelper(new TagHelperAttributeList(), context, abpInputTagHelper, _htmlEncoder, "div", TagMode.StartTagAndEndTag);
abpInputTagHelper.Render(new TagHelperAttributeList(), context, _htmlEncoder, "div", TagMode.StartTagAndEndTag);
}
protected virtual List<ModelExpression> GetModels(TagHelperContext context, TagHelperOutput output)
@ -280,12 +281,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual bool AreSelectItemsProvided(ModelExplorer explorer)
{
return GetAttribute<SelectItems>(explorer) != null;
return explorer.GetAttribute<SelectItems>() != null;
}
protected virtual bool IsRadioGroup(ModelExplorer explorer)
{
return GetAttribute<AbpRadioButton>(explorer) != null;
return explorer.GetAttribute<AbpRadioButton>() != null;
}
}
}

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

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Encodings.Web;
@ -6,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
@ -26,7 +28,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
var innerHtml = GetFormInputGroupAsHtml(context, output, out var isCheckbox);
var order = GetInputOrder(TagHelper.AspFor.ModelExplorer);
var order = TagHelper.AspFor.ModelExplorer.GetDisplayOrder();
AddGroupToFormGroupContents(
context,
@ -45,7 +47,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
output.TagMode = TagMode.StartTagAndEndTag;
output.TagName = "div";
LeaveOnlyGroupAttributes(context, output);
output.Attributes.AddClass(isCheckbox ? "form-check" : "form-group");
output.Attributes.AddClass(isCheckbox ? "custom-checkbox" : "form-group");
output.Attributes.AddClass(isCheckbox ? "custom-control" : "");
output.Attributes.AddClass(isCheckbox ? "mb-2" : "");
output.Content.SetHtmlContent(output.Content.GetContent() + innerHtml);
}
@ -54,8 +57,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual string GetFormInputGroupAsHtml(TagHelperContext context, TagHelperOutput output, out bool isCheckbox)
{
var inputTag = GetInputTagHelperOutput(context, output, out isCheckbox);
var inputHtml = RenderTagHelperOutput(inputTag, _encoder);
var inputHtml = inputTag.Render(_encoder);
var label = GetLabelAsHtml(context, output, inputTag, isCheckbox);
var info = GetInfoAsHtml(context, output, inputTag, isCheckbox);
var validation = isCheckbox ? "" : GetValidationAsHtml(context, output, inputTag);
@ -78,7 +81,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
var attributeList = new TagHelperAttributeList { { "class", "text-danger" } };
return RenderTagHelper(attributeList, context, validationMessageTagHelper, _encoder, "span", TagMode.StartTagAndEndTag, true);
return validationMessageTagHelper.Render(attributeList, context, _encoder, "span", TagMode.StartTagAndEndTag, true);
}
protected virtual string GetContent(TagHelperContext context, TagHelperOutput output, string label, string inputHtml, string validation, string infoHtml, bool isCheckbox)
@ -92,14 +95,14 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual string SurroundInnerHtmlAndGet(TagHelperContext context, TagHelperOutput output, string innerHtml, bool isCheckbox)
{
return "<div class=\"" + (isCheckbox ? "form-check" : "form-group") + "\">" +
return "<div class=\"" + (isCheckbox ? "custom-checkbox custom-control" : "form-group") + "\">" +
Environment.NewLine + innerHtml + Environment.NewLine +
"</div>";
}
protected virtual TagHelper GetInputTagHelper(TagHelperContext context, TagHelperOutput output)
{
var textAreaAttribute = GetAttribute<TextArea>(TagHelper.AspFor.ModelExplorer);
var textAreaAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<TextArea>();
if (textAreaAttribute != null)
{
@ -121,7 +124,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
var tagHelper = GetInputTagHelper(context, output);
var inputTagHelperOutput = GetInnerTagHelper(GetInputAttributes(context, output), context, tagHelper, "input");
var inputTagHelperOutput = tagHelper.ProcessAndGetOutput(GetInputAttributes(context, output), context, "input");
ConvertToTextAreaIfTextArea(inputTagHelperOutput);
AddDisabledAttribute(inputTagHelperOutput);
@ -141,7 +144,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
if (isCheckbox)
{
className = "form-check-input";
className = "custom-control-input";
}
inputTagHelperOutput.Attributes.AddClass(className + " " + GetSize(context, output));
@ -158,7 +161,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual void AddDisabledAttribute(TagHelperOutput inputTagHelperOutput)
{
if (inputTagHelperOutput.Attributes.ContainsName("disabled") == false &&
(TagHelper.IsDisabled || GetAttribute<DisabledInput>(TagHelper.AspFor.ModelExplorer) != null))
(TagHelper.IsDisabled || TagHelper.AspFor.ModelExplorer.GetAttribute<DisabledInput>() != null))
{
inputTagHelperOutput.Attributes.Add("disabled", "");
}
@ -167,7 +170,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual void AddReadOnlyAttribute(TagHelperOutput inputTagHelperOutput)
{
if (inputTagHelperOutput.Attributes.ContainsName("readonly") == false &&
(TagHelper.IsReadonly != false || GetAttribute<ReadOnlyInput>(TagHelper.AspFor.ModelExplorer) != null))
(TagHelper.IsReadonly != false || TagHelper.AspFor.ModelExplorer.GetAttribute<ReadOnlyInput>() != null))
{
inputTagHelperOutput.Attributes.Add("readonly", "");
}
@ -180,7 +183,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return;
}
var attribute = GetAttribute<Placeholder>(TagHelper.AspFor.ModelExplorer);
var attribute = TagHelper.AspFor.ModelExplorer.GetAttribute<Placeholder>();
if (attribute != null)
{
@ -192,7 +195,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual void AddInfoTextId(TagHelperOutput inputTagHelperOutput)
{
if (GetAttribute<InputInfoText>(TagHelper.AspFor.ModelExplorer) == null)
if (TagHelper.AspFor.ModelExplorer.GetAttribute<InputInfoText>() == null)
{
return;
}
@ -226,7 +229,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return GetLabelAsHtmlUsingTagHelper(context, output, isCheckbox) + GetRequiredSymbol(context, output, inputTag);
}
var checkboxClass = isCheckbox ? "class=\"form-check-label\" " : "";
var checkboxClass = isCheckbox ? "class=\"custom-control-label\" " : "";
return "<label " + checkboxClass + GetIdAttributeAsString(inputTag) + ">"
+ TagHelper.Label +
@ -240,7 +243,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return "";
}
return GetAttribute<RequiredAttribute>(TagHelper.AspFor.ModelExplorer) != null ? "<span> * </span>":"";
return TagHelper.AspFor.ModelExplorer.GetAttribute<RequiredAttribute>() != null ? "<span> * </span>":"";
}
protected virtual string GetInfoAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox)
@ -263,7 +266,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
}
else
{
var infoAttribute = GetAttribute<InputInfoText>(TagHelper.AspFor.ModelExplorer);
var infoAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<InputInfoText>();
if (infoAttribute != null)
{
text = infoAttribute.Text;
@ -294,15 +297,15 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
if (isCheckbox)
{
attributeList.AddClass("form-check-label");
attributeList.AddClass("custom-control-label");
}
return RenderTagHelper(attributeList, context, labelTagHelper, _encoder, "label", TagMode.StartTagAndEndTag, true);
return labelTagHelper.Render(attributeList, context, _encoder, "label", TagMode.StartTagAndEndTag, true);
}
protected virtual void ConvertToTextAreaIfTextArea(TagHelperOutput tagHelperOutput)
{
var textAreaAttribute = GetAttribute<TextArea>(TagHelper.AspFor.ModelExplorer);
var textAreaAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<TextArea>();
if (textAreaAttribute == null)
{
@ -354,7 +357,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual string GetSize(TagHelperContext context, TagHelperOutput output)
{
var attribute = GetAttribute<FormControlSize>(TagHelper.AspFor.ModelExplorer);
var attribute = TagHelper.AspFor.ModelExplorer.GetAttribute<FormControlSize>();
if (attribute != null)
{
@ -378,5 +381,27 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
return inputTag.Attributes.Any(a => a.Name.ToLowerInvariant() == "type" && a.Value.ToString().ToLowerInvariant() == "hidden");
}
protected virtual string GetIdAttributeAsString(TagHelperOutput inputTag)
{
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
return idAttr != null ? "for=\"" + idAttr.Value + "\"" : "";
}
protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool surpress)
{
var list = context.GetValue<List<FormGroupItem>>(FormGroupContents) ?? new List<FormGroupItem>();
surpress = list == null;
if (list != null && !list.Any(igc => igc.HtmlContent.Contains("id=\"" + propertyName.Replace('.', '_') + "\"")))
{
list.Add(new FormGroupItem
{
HtmlContent = html,
Order = order
});
}
}
}
}

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

@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
@ -24,7 +25,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
var selectItems = GetSelectItems(context,output);
SetSelectedValue(context, output, selectItems);
var order = GetInputOrder(TagHelper.AspFor.ModelExplorer);
var order = TagHelper.AspFor.ModelExplorer.GetDisplayOrder();
var html = GetHtml(context, output, selectItems);
@ -78,7 +79,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return GetSelectItemsFromEnum(context, output, TagHelper.AspFor.ModelExplorer);
}
var selectItemsAttribute = GetAttribute<SelectItems>(TagHelper.AspFor.ModelExplorer);
var selectItemsAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<SelectItems>();
if (selectItemsAttribute != null)
{
return GetSelectItemsFromAttribute(selectItemsAttribute, TagHelper.AspFor.ModelExplorer);
@ -155,5 +156,20 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return TagHelper.AspFor.ModelExplorer.Model?.ToString();
}
protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool surpress)
{
var list = context.GetValue<List<FormGroupItem>>(FormGroupContents) ?? new List<FormGroupItem>();
surpress = list == null;
if (list != null && !list.Any(igc => igc.HtmlContent.Contains("id=\"" + propertyName.Replace('.', '_') + "\"")))
{
list.Add(new FormGroupItem
{
HtmlContent = html,
Order = order
});
}
}
}
}

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

@ -10,6 +10,7 @@ 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;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
@ -30,7 +31,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
var innerHtml = GetFormInputGroupAsHtml(context, output);
var order = GetInputOrder(TagHelper.AspFor.ModelExplorer);
var order = TagHelper.AspFor.ModelExplorer.GetDisplayOrder();
AddGroupToFormGroupContents(context, TagHelper.AspFor.Name, SurroundInnerHtmlAndGet(context, output, innerHtml), order, out var surpress);
@ -51,7 +52,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual string GetFormInputGroupAsHtml(TagHelperContext context, TagHelperOutput output)
{
var selectTag = GetSelectTag(context, output);
var selectAsHtml = RenderTagHelperOutput(selectTag, _encoder);
var selectAsHtml = selectTag.Render(_encoder);
var label = GetLabelAsHtml(context, output, selectTag);
var validation = GetValidationAsHtml(context, output, selectTag);
var infoText = GetInfoAsHtml(context, output, selectTag);
@ -73,7 +74,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
ViewContext = TagHelper.ViewContext
};
var selectTagHelperOutput = GetInnerTagHelper(GetInputAttributes(context, output), context, selectTagHelper, "select", TagMode.StartTagAndEndTag);
var selectTagHelperOutput = selectTagHelper.ProcessAndGetOutput(GetInputAttributes(context, output), context, "select", TagMode.StartTagAndEndTag);
selectTagHelperOutput.Attributes.AddClass("form-control");
selectTagHelperOutput.Attributes.AddClass(GetSize(context, output));
@ -85,7 +86,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual void AddDisabledAttribute(TagHelperOutput inputTagHelperOutput)
{
var disabledAttribute = GetAttribute<DisabledInput>(TagHelper.AspFor.ModelExplorer);
var disabledAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<DisabledInput>();
if (disabledAttribute != null && !inputTagHelperOutput.Attributes.ContainsName("disabled"))
{
@ -105,7 +106,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return GetSelectItemsFromEnum(context, output, TagHelper.AspFor.ModelExplorer);
}
var selectItemsAttribute = GetAttribute<SelectItems>(TagHelper.AspFor.ModelExplorer);
var selectItemsAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<SelectItems>();
if (selectItemsAttribute != null)
{
return GetSelectItemsFromAttribute(selectItemsAttribute, TagHelper.AspFor.ModelExplorer);
@ -132,12 +133,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return "";
}
return GetAttribute<RequiredAttribute>(TagHelper.AspFor.ModelExplorer) != null ? "<span> * </span>" : "";
return TagHelper.AspFor.ModelExplorer.GetAttribute<RequiredAttribute>() != null ? "<span> * </span>" : "";
}
protected virtual void AddInfoTextId(TagHelperOutput inputTagHelperOutput)
{
if (GetAttribute<InputInfoText>(TagHelper.AspFor.ModelExplorer) == null)
if (TagHelper.AspFor.ModelExplorer.GetAttribute<InputInfoText>() == null)
{
return;
}
@ -164,7 +165,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
}
else
{
var infoAttribute = GetAttribute<InputInfoText>(TagHelper.AspFor.ModelExplorer);
var infoAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<InputInfoText>();
if (infoAttribute != null)
{
text = infoAttribute.Text;
@ -227,7 +228,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
ViewContext = TagHelper.ViewContext
};
return RenderTagHelper(new TagHelperAttributeList(), context, labelTagHelper, _encoder, "label", TagMode.StartTagAndEndTag, true);
return labelTagHelper.Render(new TagHelperAttributeList(), context, _encoder, "label", TagMode.StartTagAndEndTag, true);
}
protected virtual string GetValidationAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag)
@ -240,12 +241,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
var attributeList = new TagHelperAttributeList { { "class", "text-danger" } };
return RenderTagHelper(attributeList, context, validationMessageTagHelper, _encoder, "span", TagMode.StartTagAndEndTag, true);
return validationMessageTagHelper.Render(attributeList, context, _encoder, "span", TagMode.StartTagAndEndTag, true);
}
protected virtual string GetSize(TagHelperContext context, TagHelperOutput output)
{
var attribute = GetAttribute<FormControlSize>(TagHelper.AspFor.ModelExplorer);
var attribute = TagHelper.AspFor.ModelExplorer.GetAttribute<FormControlSize>();
if (attribute != null)
{
@ -255,11 +256,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
switch (TagHelper.Size)
{
case AbpFormControlSize.Small:
return "form-control-sm";
return "custom-select-sm";
case AbpFormControlSize.Medium:
return "form-control-md";
return "custom-select-md";
case AbpFormControlSize.Large:
return "form-control-lg";
return "custom-select-lg";
}
return "";
@ -277,6 +278,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
attrList.Add(tagHelperAttribute);
}
attrList.AddClass("custom-select");
return attrList;
}
@ -294,5 +297,27 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
output.Attributes.Add(newAttritube);
}
}
protected virtual string GetIdAttributeAsString(TagHelperOutput inputTag)
{
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
return idAttr != null ? "for=\"" + idAttr.Value + "\"" : "";
}
protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool surpress)
{
var list = context.GetValue<List<FormGroupItem>>(FormGroupContents) ?? new List<FormGroupItem>();
surpress = list == null;
if (list != null && !list.Any(igc => igc.HtmlContent.Contains("id=\"" + propertyName.Replace('.', '_') + "\"")))
{
list.Add(new FormGroupItem
{
HtmlContent = html,
Order = order
});
}
}
}
}

44
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelperService.cs

@ -10,23 +10,31 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid
output.TagName = "div";
output.Attributes.AddClass("col");
ProcessSizeClass(output, TagHelper.Size, "");
ProcessSizeClass(output, TagHelper.SizeSm, "-sm");
ProcessSizeClass(output, TagHelper.SizeMd, "-md");
ProcessSizeClass(output, TagHelper.SizeLg, "-lg");
ProcessSizeClass(output, TagHelper.SizeXl, "-xl");
ProcessOffsetClass(output, TagHelper.Offset, "");
ProcessOffsetClass(output, TagHelper.OffsetSm, "-sm");
ProcessOffsetClass(output, TagHelper.OffsetMd, "-md");
ProcessOffsetClass(output, TagHelper.OffsetLg, "-lg");
ProcessOffsetClass(output, TagHelper.OffsetXl, "-xl");
ProcessColumnOrder(output);
ProcessVerticalAlign(output);
ProcessSizeClasses(context, output);
ProcessOffsetClasses(context, output);
ProcessColumnOrder(context, output);
ProcessVerticalAlign(context, output);
}
protected virtual void ProcessSizeClass(TagHelperOutput output, ColumnSize size, string breakpoint)
protected virtual void ProcessSizeClasses(TagHelperContext context, TagHelperOutput output)
{
ProcessSizeClass(context, output, TagHelper.Size, "");
ProcessSizeClass(context, output, TagHelper.SizeSm, "-sm");
ProcessSizeClass(context, output, TagHelper.SizeMd, "-md");
ProcessSizeClass(context, output, TagHelper.SizeLg, "-lg");
ProcessSizeClass(context, output, TagHelper.SizeXl, "-xl");
}
protected virtual void ProcessOffsetClasses(TagHelperContext context, TagHelperOutput output)
{
ProcessOffsetClass(context, output, TagHelper.Offset, "");
ProcessOffsetClass(context, output, TagHelper.OffsetSm, "-sm");
ProcessOffsetClass(context, output, TagHelper.OffsetMd, "-md");
ProcessOffsetClass(context, output, TagHelper.OffsetLg, "-lg");
ProcessOffsetClass(context, output, TagHelper.OffsetXl, "-xl");
}
protected virtual void ProcessSizeClass(TagHelperContext context, TagHelperOutput output, ColumnSize size, string breakpoint)
{
if (size == ColumnSize.Undefined)
{
@ -47,7 +55,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid
output.Attributes.AddClass(classString);
}
protected virtual void ProcessOffsetClass(TagHelperOutput output, ColumnSize size, string breakpoint)
protected virtual void ProcessOffsetClass(TagHelperContext context, TagHelperOutput output, ColumnSize size, string breakpoint)
{
if (size == ColumnSize.Undefined)
{
@ -68,7 +76,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid
output.Attributes.AddClass(classString);
}
protected virtual void ProcessVerticalAlign(TagHelperOutput output)
protected virtual void ProcessVerticalAlign(TagHelperContext context, TagHelperOutput output)
{
if (TagHelper.VAlign == VerticalAlign.Default)
{
@ -78,7 +86,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid
output.Attributes.AddClass("align-self-" + TagHelper.VAlign.ToString().ToLowerInvariant());
}
protected virtual void ProcessColumnOrder(TagHelperOutput output)
protected virtual void ProcessColumnOrder(TagHelperContext context, TagHelperOutput output)
{
if (TagHelper.ColumnOrder == ColumnOrder.Undefined)
{

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

@ -1,5 +1,9 @@
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid
{
[HtmlTargetElement("abp-row")]
[HtmlTargetElement("abp-form-row")]
public class AbpRowTagHelper : AbpTagHelper<AbpRowTagHelper, AbpRowTagHelperService>
{
public VerticalAlign VAlign { get; set; } = VerticalAlign.Default;

10
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpRowTagHelperService.cs

@ -7,8 +7,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (output.TagName == "abp-row")
{
output.Attributes.AddClass("row");
}
if (output.TagName == "abp-form-row")
{
output.Attributes.AddClass("form-row");
}
output.TagName = "div";
output.Attributes.AddClass("row");
ProcessVerticalAlign(output);
ProcessHorizontalAlign(output);

11
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Label/AbpLabelTagHelper.cs

@ -1,11 +0,0 @@
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Label
{
public class AbpLabelTagHelper : AbpTagHelper<AbpLabelTagHelper, AbpLabelTagHelperService>
{
public AbpLabelTagHelper(AbpLabelTagHelperService tagHelperService)
: base(tagHelperService)
{
}
}
}

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Label/AbpLabelTagHelperService.cs

@ -1,12 +0,0 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Label
{
public class AbpLabelTagHelperService : AbpTagHelperService<AbpLabelTagHelper>
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
//TODO: fill
}
}
}

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

@ -6,6 +6,7 @@ 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;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
{
@ -121,11 +122,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
var anchorTagHelper = GetAnchorTagHelper(currentPage, out var attributeList);
var tagHelperOutput = GetInnerTagHelper(attributeList, context, anchorTagHelper, "a", TagMode.StartTagAndEndTag);
var tagHelperOutput = anchorTagHelper.ProcessAndGetOutput(attributeList, context, "a", TagMode.StartTagAndEndTag);
tagHelperOutput.Content.SetHtmlContent(localizer[localizationKey]);
var renderedHtml = RenderTagHelperOutput(tagHelperOutput, _encoder);
var renderedHtml = tagHelperOutput.Render(_encoder);
return renderedHtml;
}

14
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs

@ -7,7 +7,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (!TagHelper.Disabled??true)
if (!TagHelper.Disabled ?? true)
{
SetDataToggle(context, output);
SetDataPlacement(context, output);
@ -16,21 +16,17 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover
}
else
{
SetDisabled(context,output);
SetDisabled(context, output);
}
}
protected virtual void SetDisabled(TagHelperContext context, TagHelperOutput output)
{
var triggerAsHtml = TagHelper.Dismissible ?? false ? "datatrigger=\"focus\" " : "";
var dataPlacementAsHtml = "data-placement=\"" +GetDirectory().ToString().ToLowerInvariant() + "\" ";
var dataPlacementAsHtml = "data-placement=\"" + GetDirectory().ToString().ToLowerInvariant() + "\" ";
var titleAttribute = output.Attributes.FirstOrDefault(at => at.Name == "title");
var titleAsHtml = titleAttribute == null? "":"title=\""+ titleAttribute.Value +"\" ";
var preElementHtml = "<span class=\"d-inline-block\" "+ titleAsHtml + triggerAsHtml + dataPlacementAsHtml + "data-toggle=\"popover\" data-content=\"" +GetDataContent()+"\">";
var titleAsHtml = titleAttribute == null ? "" : "title=\"" + titleAttribute.Value + "\" ";
var preElementHtml = "<span class=\"d-inline-block\" " + titleAsHtml + triggerAsHtml + dataPlacementAsHtml + "data-toggle=\"popover\" data-content=\"" + GetDataContent() + "\">";
var postElementHtml = "</span>";
output.PreElement.SetHtmlContent(preElementHtml);

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/ProgressBar/AbpProgressGroupTagHelperService.cs

@ -10,6 +10,5 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.ProgressBar
output.Attributes.AddClass("progress");
output.TagName = "div";
}
}
}

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabDropdownTagHelperService.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
{
@ -17,7 +18,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
await output.GetChildContentAsync();
var tabHeader = GetTabHeaderItem(context, output);
var tabHeaderItems = GetValueFromContext<List<TabItem>>(context, TabItems);
var tabHeaderItems = context.GetValue<List<TabItem>>(TabItems);
tabHeaderItems.Add(new TabItem(tabHeader, "", false, TagHelper.Name, "", true));

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
{
@ -12,7 +13,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
var tabHeader = GetTabHeaderItem(context, output);
var tabHeaderItems = GetValueFromContext<List<TabItem>>(context, TabItems);
var tabHeaderItems = context.GetValue<List<TabItem>>(TabItems);
tabHeaderItems.Add(new TabItem(tabHeader, "", false, TagHelper.Name, TagHelper.ParentDropdownName, false));

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabTagHelperService.cs

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
{
@ -14,7 +15,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
var tabHeader = GetTabHeaderItem(context, output);
var tabContent = GetTabContentItem(innerContent.GetContent());
var tabHeaderItems = GetValueFromContext<List<TabItem>>(context, TabItems);
var tabHeaderItems = context.GetValue<List<TabItem>>(TabItems);
var active = TagHelper.Active ?? false;

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

@ -52,7 +52,7 @@
*************************************************************************/
var localize = function (key) {
return abp.localization.getResource('AbpUi')(key);
}
};
var recordActions = function () {
if (!$.fn.dataTableExt) {
@ -69,7 +69,7 @@
} else {
return visibilityField;
}
}
};
var _createDropdownItem = function (record, fieldItem) {
var $li = $('<li/>');
@ -83,7 +83,7 @@
$a.append($("<i>").addClass("fa fa-" + fieldItem.icon + " mr-1"));
} else if (fieldItem.iconClass) {
$a.append($("<i>").addClass(fieldItem.iconClass + " mr-1"));
}
}
$a.append(fieldItem.text);
}
@ -109,7 +109,7 @@
$a.appendTo($li);
return $li;
}
};
var _createButtonDropdown = function (record, field) {
var $container = $('<div/>')
@ -200,11 +200,11 @@
}
throw "DTE#1: Cannot create row action. Either set element or items fields!";
}
};
var hideColumnWithoutRedraw = function (tableInstance, colIndex) {
tableInstance.fnSetColumnVis(colIndex, false, false);
}
};
var hideEmptyColumn = function (cellContent, tableInstance, colIndex) {
if (cellContent == "") {
@ -307,8 +307,8 @@
});
});
}
}
}
};
};
}();
/************************************************************************
@ -338,9 +338,10 @@
}
}
configuration.dom = '<"dataTable_filters"f>rt<"row dataTable_footer"<"col-auto"l><"col-auto"i><"col"p>>';
return configuration;
}
};
}();

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

@ -60,7 +60,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
public ICurrentTenant CurrentTenant { get; set; }
public ISettingManager SettingManager { get; set; }
public ISettingProvider SettingProvider { get; set; }
public IModelStateValidator ModelValidator { get; set; }

37
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs

@ -4,10 +4,12 @@ using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization;
using Volo.Abp.Localization;
using Volo.Abp.Settings;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
@ -19,18 +21,24 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
private readonly IAbpAuthorizationPolicyProvider _abpAuthorizationPolicyProvider;
private readonly IAuthorizationService _authorizationService;
private readonly ICurrentUser _currentUser;
private readonly ISettingProvider _settingProvider;
private readonly ISettingDefinitionManager _settingDefinitionManager;
public AbpApplicationConfigurationAppService(
IOptions<AbpLocalizationOptions> localizationOptions,
IServiceProvider serviceProvider,
IAbpAuthorizationPolicyProvider abpAuthorizationPolicyProvider,
IAuthorizationService authorizationService,
ICurrentUser currentUser)
ICurrentUser currentUser,
ISettingProvider settingProvider,
SettingDefinitionManager settingDefinitionManager)
{
_serviceProvider = serviceProvider;
_abpAuthorizationPolicyProvider = abpAuthorizationPolicyProvider;
_authorizationService = authorizationService;
_currentUser = currentUser;
_settingProvider = settingProvider;
_settingDefinitionManager = settingDefinitionManager;
_localizationOptions = localizationOptions.Value;
}
@ -40,9 +48,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
return new ApplicationConfigurationDto
{
Auth = await GetAuthConfig(),
Auth = await GetAuthConfigAsync(),
Localization = GetLocalizationConfig(),
CurrentUser = GetCurrentUser()
CurrentUser = GetCurrentUser(),
Setting = await GetSettingConfigAsync()
};
}
@ -57,7 +66,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
};
}
protected virtual async Task<ApplicationAuthConfigurationDto> GetAuthConfig()
protected virtual async Task<ApplicationAuthConfigurationDto> GetAuthConfigAsync()
{
var authConfig = new ApplicationAuthConfigurationDto();
@ -97,5 +106,25 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
return localizationConfig;
}
private async Task<ApplicationSettingConfigurationDto> GetSettingConfigAsync()
{
var result = new ApplicationSettingConfigurationDto
{
Values = new Dictionary<string, string>()
};
foreach (var settingDefinition in _settingDefinitionManager.GetAll())
{
if (!settingDefinition.IsVisibleToClients)
{
continue;
}
result.Values[settingDefinition.Name] = await _settingProvider.GetOrNullAsync(settingDefinition.Name);
}
return result;
}
}
}

13
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs

@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.AspNetCore.Auditing;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Tracing;
using Volo.Abp.AspNetCore.Uow;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Localization;
@ -40,6 +41,12 @@ namespace Microsoft.AspNetCore.Builder
.UseMiddleware<AbpUnitOfWorkMiddleware>();
}
public static IApplicationBuilder UseCorrelationId(this IApplicationBuilder app)
{
return app
.UseMiddleware<AbpCorrelationIdMiddleware>();
}
public static IApplicationBuilder UseAbpRequestLocalization(this IApplicationBuilder app)
{
IReadOnlyList<LanguageInfo> languages;
@ -48,10 +55,10 @@ namespace Microsoft.AspNetCore.Builder
using (var scope = app.ApplicationServices.CreateScope())
{
var languageProvider = scope.ServiceProvider.GetRequiredService<ILanguageProvider>();
var settingManager = scope.ServiceProvider.GetRequiredService<ISettingManager>();
languages = languageProvider.GetLanguages();
languages = AsyncHelper.RunSync(() => languageProvider.GetLanguagesAsync());
defaultLanguage = settingManager.GetOrNull(LocalizationSettingNames.DefaultLanguage);
var settingProvider = scope.ServiceProvider.GetRequiredService<ISettingProvider>();
defaultLanguage = settingProvider.GetOrNull(LocalizationSettingNames.DefaultLanguage);
}
if (!languages.Any())

1
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs

@ -13,7 +13,6 @@ using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.UI;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling

58
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AbpCorrelationIdMiddleware.cs

@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.Tracing;
namespace Volo.Abp.AspNetCore.Tracing
{
public class AbpCorrelationIdMiddleware
{
private readonly RequestDelegate _next;
public AbpCorrelationIdMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(
HttpContext httpContext,
IOptions<CorrelationIdOptions> options,
ICorrelationIdProvider correlationIdProvider)
{
var correlationId = correlationIdProvider.Get();
var optionsValue = options.Value;
try
{
await _next(httpContext);
}
finally
{
CheckAndSetCorrelationIdOnResponse(httpContext, optionsValue, correlationId);
}
}
protected virtual void CheckAndSetCorrelationIdOnResponse(
HttpContext httpContext,
CorrelationIdOptions options,
string correlationId)
{
if (httpContext.Response.HasStarted)
{
return;
}
if (!options.SetResponseHeader)
{
return;
}
if (httpContext.Response.Headers.ContainsKey(options.HttpHeaderName))
{
return;
}
httpContext.Response.Headers[options.HttpHeaderName] = correlationId;
}
}
}

49
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs

@ -0,0 +1,49 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Tracing;
namespace Volo.Abp.AspNetCore.Tracing
{
[Dependency(ReplaceServices = true)]
public class AspNetCoreCorrelationIdProvider : ICorrelationIdProvider, ITransientDependency
{
protected IHttpContextAccessor HttpContextAccessor { get; }
protected CorrelationIdOptions Options { get; }
public AspNetCoreCorrelationIdProvider(
IHttpContextAccessor httpContextAccessor,
IOptions<CorrelationIdOptions> options)
{
HttpContextAccessor = httpContextAccessor;
Options = options.Value;
}
public virtual string Get()
{
if (HttpContextAccessor.HttpContext?.Request?.Headers == null)
{
return CreateNewCorrelationId();
}
lock (HttpContextAccessor.HttpContext.Request.Headers)
{
string correlationId = HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName];
if (correlationId.IsNullOrEmpty())
{
correlationId = CreateNewCorrelationId();
HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName] = correlationId;
}
return correlationId;
}
}
protected virtual string CreateNewCorrelationId()
{
return Guid.NewGuid().ToString("N");
}
}
}

6
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs

@ -14,6 +14,12 @@ namespace Volo.Abp.Auditing
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// The name of the application or service writing audit logs.
/// Default: null.
/// </summary>
public string ApplicationName { get; set; }
/// <summary>
/// Default: true.
/// </summary>

6
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditLogInfo.cs

@ -10,6 +10,8 @@ namespace Volo.Abp.Auditing
//TODO: Make serializable!
public class AuditLogInfo : IMultiTenant, IHasExtraProperties
{
public string ApplicationName { get; set; }
public Guid? UserId { get; set; }
public string UserName { get; set; }
@ -24,6 +26,10 @@ namespace Volo.Abp.Auditing
public int ExecutionDuration { get; set; }
public string ClientId { get; set; }
public string CorrelationId { get; set; }
public string ClientIpAddress { get; set; }
public string ClientName { get; set; }

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

@ -5,9 +5,11 @@ using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Clients;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
using Volo.Abp.Users;
namespace Volo.Abp.Auditing
@ -18,30 +20,36 @@ namespace Volo.Abp.Auditing
protected IAuditingStore AuditingStore { get; }
protected ICurrentUser CurrentUser { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICurrentClient CurrentClient { get; }
protected IClock Clock { get; }
protected AbpAuditingOptions Options;
protected IAuditSerializer AuditSerializer;
protected IServiceProvider ServiceProvider;
protected ICorrelationIdProvider CorrelationIdProvider { get; }
public AuditingHelper(
IAuditSerializer auditSerializer,
IOptions<AbpAuditingOptions> options,
ICurrentUser currentUser,
ICurrentTenant currentTenant,
ICurrentClient currentClient,
IClock clock,
IAuditingStore auditingStore,
ILogger<AuditingHelper> logger,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
ICorrelationIdProvider correlationIdProvider)
{
Options = options.Value;
AuditSerializer = auditSerializer;
CurrentUser = currentUser;
CurrentTenant = currentTenant;
CurrentClient = currentClient;
Clock = clock;
AuditingStore = auditingStore;
Logger = logger;
ServiceProvider = serviceProvider;
CorrelationIdProvider = correlationIdProvider;
}
public virtual bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false)
@ -82,9 +90,12 @@ namespace Volo.Abp.Auditing
{
var auditInfo = new AuditLogInfo
{
ApplicationName = Options.ApplicationName,
TenantId = CurrentTenant.Id,
UserId = CurrentUser.Id,
UserName = CurrentUser.UserName,
ClientId = CurrentClient.Id,
CorrelationId = CorrelationIdProvider.Get(),
//ImpersonatorUserId = AbpSession.ImpersonatorUserId, //TODO: Impersonation system is not available yet!
//ImpersonatorTenantId = AbpSession.ImpersonatorTenantId,
ExecutionTime = Clock.Now

1
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs

@ -28,6 +28,7 @@ namespace Volo.Abp.Authorization
{
options.ValueProviders.Add<UserPermissionValueProvider>();
options.ValueProviders.Add<RolePermissionValueProvider>();
options.ValueProviders.Add<ClientPermissionValueProvider>();
});
}
}

11
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs

@ -1,6 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Clients;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
@ -10,11 +11,16 @@ namespace Volo.Abp.Authorization
{
private readonly IAuthorizationService _authorizationService;
private readonly ICurrentUser _currentUser;
private readonly ICurrentClient _currentClient;
public MethodInvocationAuthorizationService(IAuthorizationService authorizationService, ICurrentUser currentUser)
public MethodInvocationAuthorizationService(
IAuthorizationService authorizationService,
ICurrentUser currentUser,
ICurrentClient currentClient)
{
_authorizationService = authorizationService;
_currentUser = currentUser;
_currentClient = currentClient;
}
public async Task CheckAsync(MethodInvocationAuthorizationContext context)
@ -53,7 +59,8 @@ namespace Volo.Abp.Authorization
{
if (authorizationAttribute.Policy == null)
{
if (!_currentUser.IsAuthenticated) //TODO: What about API calls without user id?
//TODO: Can we find a better, unified, way of checking if current request has been authenticated
if (!_currentUser.IsAuthenticated && !_currentClient.IsAuthenticated)
{
throw new AbpAuthorizationException("Authorization failed! User has not logged in.");
}

35
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs

@ -0,0 +1,35 @@
using System.Threading.Tasks;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.Authorization.Permissions
{
public class ClientPermissionValueProvider : PermissionValueProvider
{
public const string ProviderName = "Client";
public override string Name => ProviderName;
public ClientPermissionValueProvider(IPermissionStore permissionStore)
: base(permissionStore)
{
}
public override async Task<PermissionValueProviderGrantInfo> CheckAsync(PermissionValueCheckContext context)
{
var clientId = context.Principal?.FindFirst(AbpClaimTypes.ClientId)?.Value;
if (clientId == null)
{
return PermissionValueProviderGrantInfo.NonGranted;
}
if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, clientId))
{
return new PermissionValueProviderGrantInfo(true, clientId);
}
return PermissionValueProviderGrantInfo.NonGranted;
}
}
}

14
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs

@ -1,10 +1,10 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
@ -40,7 +40,7 @@ namespace Volo.Abp.Authorization.Permissions
true
);
}
public virtual Task<PermissionGrantInfo> CheckAsync(string name)
{
return CheckAsync(PrincipalAccessor.Principal, name);
@ -57,6 +57,12 @@ namespace Volo.Abp.Authorization.Permissions
foreach (var provider in ValueProviders)
{
if (context.Permission.Providers.Any() &&
!context.Permission.Providers.Contains(provider.Name))
{
continue;
}
var result = await provider.CheckAsync(context);
if (result.IsGranted)
{

31
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs

@ -18,6 +18,12 @@ namespace Volo.Abp.Authorization.Permissions
/// </summary>
public PermissionDefinition Parent { get; private set; }
/// <summary>
/// A list of allowed providers to get/set value of this permission.
/// An empty list indicates that all providers are allowed.
/// </summary>
public List<string> Providers { get; }
public ILocalizableString DisplayName
{
get => _displayName;
@ -53,6 +59,7 @@ namespace Volo.Abp.Authorization.Permissions
DisplayName = displayName ?? new FixedLocalizableString(name);
Properties = new Dictionary<string, object>();
Providers = new List<string>();
_children = new List<PermissionDefinition>();
}
@ -68,6 +75,30 @@ namespace Volo.Abp.Authorization.Permissions
return child;
}
/// <summary>
/// Sets a property in the <see cref="Properties"/> dictionary.
/// This is a shortcut for nested calls on this object.
/// </summary>
public virtual PermissionDefinition WithProperty(string key, object value)
{
Properties[key] = value;
return this;
}
/// <summary>
/// Sets a property in the <see cref="Properties"/> dictionary.
/// This is a shortcut for nested calls on this object.
/// </summary>
public virtual PermissionDefinition WithProviders(params string[] providers)
{
if (!providers.IsNullOrEmpty())
{
Providers.AddRange(providers);
}
return this;
}
public override string ToString()
{
return $"[{nameof(PermissionDefinition)} {Name}]";

22
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs

@ -103,6 +103,7 @@ namespace Volo.Abp.Caching
Logger.LogException(ex, LogLevel.Warning);
return null;
}
throw;
}
@ -194,13 +195,13 @@ namespace Volo.Abp.Caching
}
}
public virtual Task SetAsync(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, CancellationToken token = default)
public virtual async Task SetAsync(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
return Cache.SetAsync(
await Cache.SetAsync(
NormalizeKey(key),
ObjectSerializer.Serialize(value),
options ?? DefaultCacheOptions,
@ -212,8 +213,9 @@ namespace Volo.Abp.Caching
if ((bool)hideErrors)
{
Logger.LogException(ex, LogLevel.Warning);
return Task.CompletedTask;
return;
}
throw;
}
}
@ -238,21 +240,22 @@ namespace Volo.Abp.Caching
}
}
public virtual Task RefreshAsync(string key, bool? hideErrors = null, CancellationToken token = default)
public virtual async Task RefreshAsync(string key, bool? hideErrors = null, CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
return Cache.RefreshAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token));
await Cache.RefreshAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token));
}
catch (Exception ex)
{
if ((bool)hideErrors)
{
Logger.LogException(ex, LogLevel.Warning);
return Task.CompletedTask;
return;
}
throw;
}
}
@ -276,21 +279,22 @@ namespace Volo.Abp.Caching
}
}
public virtual Task RemoveAsync(string key, bool? hideErrors = null, CancellationToken token = default)
public virtual async Task RemoveAsync(string key, bool? hideErrors = null, CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
return Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token));
await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token));
}
catch (Exception ex)
{
if ((bool)hideErrors)
{
Logger.LogException(ex, LogLevel.Warning);
return Task.CompletedTask;
return;
}
throw;
}
}

9
framework/src/Volo.Abp.Core/Volo/Abp/Tracing/CorrelationIdOptions.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.Tracing
{
public class CorrelationIdOptions
{
public string HttpHeaderName { get; set; } = "X-Correlation-Id";
public bool SetResponseHeader { get; set; } = true;
}
}

18
framework/src/Volo.Abp.Core/Volo/Abp/Tracing/DefaultCorrelationIdProvider.cs

@ -0,0 +1,18 @@
using System;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Tracing
{
public class DefaultCorrelationIdProvider : ICorrelationIdProvider, ISingletonDependency
{
public string Get()
{
return CreateNewCorrelationId();
}
protected virtual string CreateNewCorrelationId()
{
return Guid.NewGuid().ToString("N");
}
}
}

10
framework/src/Volo.Abp.Core/Volo/Abp/Tracing/ICorrelationIdProvider.cs

@ -0,0 +1,10 @@
using JetBrains.Annotations;
namespace Volo.Abp.Tracing
{
public interface ICorrelationIdProvider
{
[NotNull]
string Get();
}
}

1
framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj

@ -20,6 +20,7 @@
<ProjectReference Include="..\Volo.Abp.Http.Abstractions\Volo.Abp.Http.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
<ProjectReference Include="..\Volo.Abp.Settings\Volo.Abp.Settings.csproj" />
<ProjectReference Include="..\Volo.Abp.Validation\Volo.Abp.Validation.csproj" />
</ItemGroup>

17
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs

@ -1,5 +1,4 @@
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization;
using Volo.Abp.Domain;
@ -8,17 +7,21 @@ using Volo.Abp.Http.Modeling;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Security;
using Volo.Abp.Settings;
using Volo.Abp.Uow;
using Volo.Abp.Validation;
namespace Volo.Abp.Application
{
[DependsOn(typeof(AbpDddDomainModule))]
[DependsOn(typeof(AbpSecurityModule))]
[DependsOn(typeof(AbpObjectMappingModule))]
[DependsOn(typeof(AbpValidationModule))]
[DependsOn(typeof(AbpAuthorizationModule))]
[DependsOn(typeof(AbpHttpAbstractionsModule))]
[DependsOn(
typeof(AbpDddDomainModule),
typeof(AbpSecurityModule),
typeof(AbpObjectMappingModule),
typeof(AbpValidationModule),
typeof(AbpAuthorizationModule),
typeof(AbpHttpAbstractionsModule),
typeof(AbpSettingsModule)
)]
public class AbpDddApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

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

@ -45,7 +45,7 @@ namespace Volo.Abp.Application.Services
public ICurrentUser CurrentUser { get; set; }
public ISettingManager SettingManager { get; set; }
public ISettingProvider SettingProvider { get; set; }
public IClock Clock { get; set; }

8
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs

@ -13,10 +13,10 @@ namespace Volo.Abp.Domain.Entities
IHasExtraProperties,
IHasConcurrencyStamp
{
public Dictionary<string, object> ExtraProperties { get; protected set; }
public virtual Dictionary<string, object> ExtraProperties { get; protected set; }
[DisableAuditing]
public string ConcurrencyStamp { get; set; }
public virtual string ConcurrencyStamp { get; set; }
private readonly ICollection<object> _localEvents = new Collection<object>();
private readonly ICollection<object> _distributedEvents = new Collection<object>();
@ -65,10 +65,10 @@ namespace Volo.Abp.Domain.Entities
IHasExtraProperties,
IHasConcurrencyStamp
{
public Dictionary<string, object> ExtraProperties { get; protected set; }
public virtual Dictionary<string, object> ExtraProperties { get; protected set; }
[DisableAuditing]
public string ConcurrencyStamp { get; set; }
public virtual string ConcurrencyStamp { get; set; }
private readonly ICollection<object> _localEvents = new Collection<object>();
private readonly ICollection<object> _distributedEvents = new Collection<object>();

101
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Values/ValueObject.cs

@ -1,98 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Volo.Abp.Domain.Values
{
//Inspired from https://blogs.msdn.microsoft.com/cesardelatorre/2011/06/06/implementing-a-value-object-base-class-supertype-patternddd-patterns-related/
/// <summary>
/// Base class for value objects.
/// </summary>
/// <typeparam name="TValueObject">The type of the value object.</typeparam>
public abstract class ValueObject<TValueObject> : IEquatable<TValueObject>
where TValueObject : ValueObject<TValueObject>
//Inspired from https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects
public abstract class ValueObject
{
public bool Equals(TValueObject other)
protected static bool EqualOperator(ValueObject left, ValueObject right)
{
if ((object)other == null)
if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
{
return false;
}
return ReferenceEquals(left, null) || left.Equals(right);
}
var publicProperties = GetType().GetTypeInfo().GetProperties();
if (!publicProperties.Any())
{
return true;
}
return publicProperties.All(property => Equals(property.GetValue(this, null), property.GetValue(other, null)));
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
protected abstract IEnumerable<object> GetAtomicValues();
public override bool Equals(object obj)
{
if (obj == null)
if (obj == null || obj.GetType() != GetType())
{
return false;
}
var item = obj as ValueObject<TValueObject>;
return (object)item != null && Equals((TValueObject)item);
}
public override int GetHashCode()
{
//TODO: Can we cache the hash value assuming value objects are always immutable? We can make a Reset-like method to reset it's mutated.
const int index = 1;
const int initialHasCode = 31;
var publicProperties = GetType().GetTypeInfo().GetProperties();
if (!publicProperties.Any())
ValueObject other = (ValueObject)obj;
IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator();
while (thisValues.MoveNext() && otherValues.MoveNext())
{
return initialHasCode;
}
var hashCode = initialHasCode;
var changeMultiplier = false;
foreach (var property in publicProperties)
{
var value = property.GetValue(this, null);
if (value == null)
if (ReferenceEquals(thisValues.Current, null) ^
ReferenceEquals(otherValues.Current, null))
{
//support {"a",null,null,"a"} != {null,"a","a",null}
hashCode = hashCode ^ (index * 13);
continue;
return false;
}
hashCode = hashCode * (changeMultiplier ? 59 : 114) + value.GetHashCode();
changeMultiplier = !changeMultiplier;
}
return hashCode;
}
public static bool operator ==(ValueObject<TValueObject> x, ValueObject<TValueObject> y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (((object)x == null) || ((object)y == null))
{
return false;
if (thisValues.Current != null &&
!thisValues.Current.Equals(otherValues.Current))
{
return false;
}
}
return x.Equals(y);
return !thisValues.MoveNext() && !otherValues.MoveNext();
}
public static bool operator !=(ValueObject<TValueObject> x, ValueObject<TValueObject> y)
public override int GetHashCode()
{
return !(x == y);
return GetAtomicValues()
.Select(x => x != null ? x.GetHashCode() : 0)
.Aggregate((x, y) => x ^ y);
}
// Other utilility methods
}
}

3
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs

@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.Emailing.Templates;
using Volo.Abp.Emailing.Templates.Virtual;
using Volo.Abp.Localization;

10
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderConfiguration.cs

@ -11,16 +11,16 @@ namespace Volo.Abp.Emailing
{
public virtual string DefaultFromAddress => GetNotEmptySettingValue(EmailSettingNames.DefaultFromAddress);
public virtual string DefaultFromDisplayName => SettingManager.GetOrNull(EmailSettingNames.DefaultFromDisplayName);
public virtual string DefaultFromDisplayName => SettingProvider.GetOrNull(EmailSettingNames.DefaultFromDisplayName);
protected readonly ISettingManager SettingManager;
protected readonly ISettingProvider SettingProvider;
/// <summary>
/// Creates a new <see cref="EmailSenderConfiguration"/>.
/// </summary>
protected EmailSenderConfiguration(ISettingManager settingManager)
protected EmailSenderConfiguration(ISettingProvider settingProvider)
{
SettingManager = settingManager;
SettingProvider = settingProvider;
}
/// <summary>
@ -30,7 +30,7 @@ namespace Volo.Abp.Emailing
/// <returns>Value of the setting</returns>
protected string GetNotEmptySettingValue(string name)
{
var value = SettingManager.GetOrNull(name);
var value = SettingProvider.GetOrNull(name);
if (value.IsNullOrEmpty())
{

10
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Smtp/SmtpEmailSenderConfiguration.cs

@ -18,14 +18,14 @@ namespace Volo.Abp.Emailing.Smtp
public virtual string Password => GetNotEmptySettingValue(EmailSettingNames.Smtp.Password);
public virtual string Domain => SettingManager.GetOrNull(EmailSettingNames.Smtp.Domain);
public virtual string Domain => SettingProvider.GetOrNull(EmailSettingNames.Smtp.Domain);
public virtual bool EnableSsl => SettingManager.GetOrNull(EmailSettingNames.Smtp.EnableSsl).To<bool>();
public virtual bool EnableSsl => SettingProvider.GetOrNull(EmailSettingNames.Smtp.EnableSsl).To<bool>();
public virtual bool UseDefaultCredentials => SettingManager.GetOrNull(EmailSettingNames.Smtp.UseDefaultCredentials).To<bool>();
public virtual bool UseDefaultCredentials => SettingProvider.GetOrNull(EmailSettingNames.Smtp.UseDefaultCredentials).To<bool>();
public SmtpEmailSenderConfiguration(ISettingManager settingManager)
: base(settingManager)
public SmtpEmailSenderConfiguration(ISettingProvider settingProvider)
: base(settingProvider)
{
}

26
framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs

@ -22,26 +22,24 @@ namespace Volo.Abp.Http.Client.IdentityModel
public async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
{
var accessToken = await GetAccessTokenFromHttpContextOrNullAsync();
if (accessToken != null)
{
context.Client.SetBearerToken(accessToken);
}
else
if (context.RemoteService.GetUseCurrentAccessToken() != false)
{
await IdentityModelHttpClientAuthenticator.AuthenticateAsync(
new IdentityModelHttpClientAuthenticateContext(
context.Client,
context.RemoteService.GetIdentityClient()
)
);
var accessToken = await GetAccessTokenFromHttpContextOrNullAsync();
if (accessToken != null)
{
context.Client.SetBearerToken(accessToken);
return;
}
}
await IdentityModelHttpClientAuthenticator.AuthenticateAsync(
context.Client,
context.RemoteService.GetIdentityClient()
);
}
protected virtual async Task<string> GetAccessTokenFromHttpContextOrNullAsync()
{
//TODO: What if the access_token in the current Http Request is not usable for this client?
var httpContext = HttpContextAccessor?.HttpContext;
if (httpContext == null)
{

35
framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/RemoteServiceConfigurationExtensions.cs

@ -5,19 +5,48 @@ namespace Volo.Abp.Http.Client
{
public static class RemoteServiceConfigurationExtensions
{
public const string IdentityClient = "IdentityClient";
public const string IdentityClientName = "IdentityClient";
public const string UseCurrentAccessTokenName = "UseCurrentAccessToken";
[CanBeNull]
public static string GetIdentityClient([NotNull] this RemoteServiceConfiguration configuration)
{
Check.NotNullOrEmpty(configuration, nameof(configuration));
return configuration.GetOrDefault(IdentityClient);
return configuration.GetOrDefault(IdentityClientName);
}
public static RemoteServiceConfiguration SetIdentityClient([NotNull] this RemoteServiceConfiguration configuration, [CanBeNull] string value)
{
configuration[IdentityClient] = value;
configuration[IdentityClientName] = value;
return configuration;
}
[CanBeNull]
public static bool? GetUseCurrentAccessToken([NotNull] this RemoteServiceConfiguration configuration)
{
Check.NotNullOrEmpty(configuration, nameof(configuration));
var value = configuration.GetOrDefault(UseCurrentAccessTokenName);
if (value == null)
{
return null;
}
return bool.Parse(value);
}
public static RemoteServiceConfiguration SetUseCurrentAccessToken([NotNull] this RemoteServiceConfiguration configuration, [CanBeNull] bool? value)
{
if (value == null)
{
configuration.Remove(UseCurrentAccessTokenName);
}
else
{
configuration[UseCurrentAccessTokenName] = value.Value.ToString().ToLowerInvariant();
}
return configuration;
}
}

1
framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj

@ -20,6 +20,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Castle.Core\Volo.Abp.Castle.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.Http\Volo.Abp.Http.csproj" />
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
</ItemGroup>
</Project>

9
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs

@ -1,17 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Castle;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
namespace Volo.Abp.Http.Client
{
[DependsOn(typeof(AbpHttpModule))]
[DependsOn(typeof(AbpCastleCoreModule))]
[DependsOn(
typeof(AbpHttpModule),
typeof(AbpCastleCoreModule),
typeof(AbpThreadingModule)
)]
public class AbpHttpClientModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<RemoteServiceOptions>(configuration);
}
}

6
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Authentication/RemoteServiceHttpClientAuthenticateContext.cs

@ -10,14 +10,18 @@ namespace Volo.Abp.Http.Client.Authentication
public RemoteServiceConfiguration RemoteService { get; }
public string RemoteServiceName { get; }
public RemoteServiceHttpClientAuthenticateContext(
HttpClient client,
HttpRequestMessage request,
RemoteServiceConfiguration remoteService)
RemoteServiceConfiguration remoteService,
string remoteServiceName)
{
Client = client;
Request = request;
RemoteService = remoteService;
RemoteServiceName = remoteServiceName;
}
}
}

95
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@ -15,21 +16,25 @@ using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Json;
using Volo.Abp.Reflection;
using Volo.Abp.Threading;
using Volo.Abp.Tracing;
namespace Volo.Abp.Http.Client.DynamicProxying
{
//TODO: Somehow capture cancellationtoken and pass to other methods...?
public class DynamicHttpProxyInterceptor<TService> : AbpInterceptor, ITransientDependency
{
private static MethodInfo GenericInterceptAsyncMethod { get; }
// ReSharper disable once StaticMemberInGenericType
protected static MethodInfo GenericInterceptAsyncMethod { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected ICorrelationIdProvider CorrelationIdProvider { get; }
protected CorrelationIdOptions CorrelationIdOptions { get; }
protected IDynamicProxyHttpClientFactory HttpClientFactory { get; }
protected IApiDescriptionFinder ApiDescriptionFinder { get; }
protected RemoteServiceOptions RemoteServiceOptions { get; }
protected AbpHttpClientOptions ClientOptions { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator { get; }
private readonly IDynamicProxyHttpClientFactory _httpClientFactory;
private readonly IApiDescriptionFinder _apiDescriptionFinder;
private readonly RemoteServiceOptions _remoteServiceOptions;
private readonly AbpHttpClientOptions _clientOptions;
private readonly IJsonSerializer _jsonSerializer;
private readonly IRemoteServiceHttpClientAuthenticator _clientAuthenticator;
public ILogger<DynamicHttpProxyInterceptor<TService>> Logger { get; set; }
@ -46,14 +51,20 @@ namespace Volo.Abp.Http.Client.DynamicProxying
IOptionsSnapshot<RemoteServiceOptions> remoteServiceOptions,
IApiDescriptionFinder apiDescriptionFinder,
IJsonSerializer jsonSerializer,
IRemoteServiceHttpClientAuthenticator clientAuthenticator)
IRemoteServiceHttpClientAuthenticator clientAuthenticator,
ICancellationTokenProvider cancellationTokenProvider,
ICorrelationIdProvider correlationIdProvider,
IOptions<CorrelationIdOptions> correlationIdOptions)
{
_httpClientFactory = httpClientFactory;
_apiDescriptionFinder = apiDescriptionFinder;
_jsonSerializer = jsonSerializer;
_clientAuthenticator = clientAuthenticator;
_clientOptions = clientOptions.Value;
_remoteServiceOptions = remoteServiceOptions.Value;
CancellationTokenProvider = cancellationTokenProvider;
CorrelationIdProvider = correlationIdProvider;
CorrelationIdOptions = correlationIdOptions.Value;
HttpClientFactory = httpClientFactory;
ApiDescriptionFinder = apiDescriptionFinder;
JsonSerializer = jsonSerializer;
ClientAuthenticator = clientAuthenticator;
ClientOptions = clientOptions.Value;
RemoteServiceOptions = remoteServiceOptions.Value;
Logger = NullLogger<DynamicHttpProxyInterceptor<TService>>.Instance;
}
@ -62,11 +73,11 @@ namespace Volo.Abp.Http.Client.DynamicProxying
{
if (invocation.Method.ReturnType == typeof(void))
{
AsyncHelper.RunSync(() => MakeRequest(invocation));
AsyncHelper.RunSync(() => MakeRequestAsync(invocation));
}
else
{
var responseAsString = AsyncHelper.RunSync(() => MakeRequest(invocation));
var responseAsString = AsyncHelper.RunSync(() => MakeRequestAsync(invocation));
//TODO: Think on that
if (TypeHelper.IsPrimitiveExtended(invocation.Method.ReturnType, true))
@ -75,7 +86,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying
}
else
{
invocation.ReturnValue = _jsonSerializer.Deserialize(
invocation.ReturnValue = JsonSerializer.Deserialize(
invocation.Method.ReturnType,
responseAsString
);
@ -87,7 +98,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying
{
if (invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty())
{
return MakeRequest(invocation);
return MakeRequestAsync(invocation);
}
invocation.ReturnValue = GenericInterceptAsyncMethod
@ -99,7 +110,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying
private async Task<T> MakeRequestAndGetResultAsync<T>(IAbpMethodInvocation invocation)
{
var responseAsString = await MakeRequest(invocation);
var responseAsString = await MakeRequestAsync(invocation);
//TODO: Think on that
if (TypeHelper.IsPrimitiveExtended(typeof(T), true))
@ -107,36 +118,37 @@ namespace Volo.Abp.Http.Client.DynamicProxying
return (T)Convert.ChangeType(responseAsString, typeof(T));
}
return _jsonSerializer.Deserialize<T>(responseAsString);
return JsonSerializer.Deserialize<T>(responseAsString);
}
private async Task<string> MakeRequest(IAbpMethodInvocation invocation)
private async Task<string> MakeRequestAsync(IAbpMethodInvocation invocation)
{
using (var client = _httpClientFactory.Create())
using (var client = HttpClientFactory.Create())
{
var clientConfig = _clientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}.");
var remoteServiceConfig = _remoteServiceOptions.RemoteServices.GetConfigurationOrDefault(clientConfig.RemoteServiceName);
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}.");
var remoteServiceConfig = RemoteServiceOptions.RemoteServices.GetConfigurationOrDefault(clientConfig.RemoteServiceName);
var action = await _apiDescriptionFinder.FindActionAsync(remoteServiceConfig.BaseUrl, typeof(TService), invocation.Method);
var action = await ApiDescriptionFinder.FindActionAsync(remoteServiceConfig.BaseUrl, typeof(TService), invocation.Method);
var apiVersion = GetApiVersionInfo(action);
var url = remoteServiceConfig.BaseUrl + UrlBuilder.GenerateUrlWithParameters(action, invocation.ArgumentsDictionary, apiVersion);
var requestMessage = new HttpRequestMessage(action.GetHttpMethod(), url)
{
Content = RequestPayloadBuilder.BuildContent(action, invocation.ArgumentsDictionary, _jsonSerializer, apiVersion)
Content = RequestPayloadBuilder.BuildContent(action, invocation.ArgumentsDictionary, JsonSerializer, apiVersion)
};
AddHeaders(invocation, action, requestMessage, apiVersion);
await _clientAuthenticator.Authenticate(
await ClientAuthenticator.Authenticate(
new RemoteServiceHttpClientAuthenticateContext(
client,
requestMessage,
remoteServiceConfig
remoteServiceConfig,
clientConfig.RemoteServiceName
)
);
var response = await client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage, GetCancellationToken());
if (!response.IsSuccessStatusCode)
{
@ -175,8 +187,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying
return action.SupportedVersions.Last(); //TODO: Ensure to get the latest version!
}
private static void AddHeaders(IAbpMethodInvocation invocation, ActionApiDescriptionModel action, HttpRequestMessage requestMessage, ApiVersionInfo apiVersion)
protected virtual void AddHeaders(IAbpMethodInvocation invocation, ActionApiDescriptionModel action, HttpRequestMessage requestMessage, ApiVersionInfo apiVersion)
{
//API Version
if (!apiVersion.Version.IsNullOrEmpty())
{
//TODO: What about other media types?
@ -185,8 +198,8 @@ namespace Volo.Abp.Http.Client.DynamicProxying
requestMessage.Headers.Add("api-version", apiVersion.Version);
}
//Header parameters
var headers = action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header).ToArray();
foreach (var headerParameter in headers)
{
var value = HttpActionParameterHelper.FindParameterValue(invocation.ArgumentsDictionary, headerParameter);
@ -195,22 +208,25 @@ namespace Volo.Abp.Http.Client.DynamicProxying
requestMessage.Headers.Add(headerParameter.Name, value.ToString());
}
}
//CorrelationId
requestMessage.Headers.Add(CorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get());
}
private string GetConfiguredApiVersion()
{
var clientConfig = _clientOptions.HttpClientProxies.GetOrDefault(typeof(TService))
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService))
?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}.");
return _remoteServiceOptions.RemoteServices.GetOrDefault(clientConfig.RemoteServiceName)?.Version
?? _remoteServiceOptions.RemoteServices.Default?.Version;
return RemoteServiceOptions.RemoteServices.GetOrDefault(clientConfig.RemoteServiceName)?.Version
?? RemoteServiceOptions.RemoteServices.Default?.Version;
}
private async Task ThrowExceptionForResponseAsync(HttpResponseMessage response)
{
if (response.Headers.Contains(AbpHttpConsts.AbpErrorFormat))
{
var errorResponse = _jsonSerializer.Deserialize<RemoteServiceErrorResponse>(
var errorResponse = JsonSerializer.Deserialize<RemoteServiceErrorResponse>(
await response.Content.ReadAsStringAsync()
);
@ -219,5 +235,10 @@ namespace Volo.Abp.Http.Client.DynamicProxying
throw new AbpException($"Remote service returns error! HttpStatusCode: {response.StatusCode}, ReasonPhrase: {response.ReasonPhrase}");
}
protected virtual CancellationToken GetCancellationToken()
{
return CancellationTokenProvider.Token;
}
}
}

3
framework/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpModule.cs

@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Http.ProxyScripting.Configuration;
using Volo.Abp.Http.ProxyScripting.Configuration;
using Volo.Abp.Http.ProxyScripting.Generators.JQuery;
using Volo.Abp.Json;
using Volo.Abp.Modularity;

3
framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs

@ -15,7 +15,6 @@ namespace Volo.Abp.IdentityModel
public class IdentityModelHttpClientAuthenticator : IIdentityModelHttpClientAuthenticator, ITransientDependency
{
public ILogger<IdentityModelHttpClientAuthenticator> Logger { get; set; }
protected IdentityClientOptions ClientOptions { get; }
public IdentityModelHttpClientAuthenticator(
@ -84,6 +83,8 @@ namespace Volo.Abp.IdentityModel
protected virtual async Task<TokenResponse> GetTokenResponse(DiscoveryResponse discoveryResponse, IdentityClientConfiguration configuration)
{
//TODO: Pass cancellation token
var tokenClient = new TokenClient(discoveryResponse.TokenEndpoint, configuration.ClientId, configuration.ClientSecret);
switch (configuration.GrantType)

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

@ -7,7 +7,7 @@ namespace Volo.Abp.Localization
{
public static IReadOnlyList<LanguageInfo> GetLanguages(this ILanguageProvider languageProvider)
{
return AsyncHelper.RunSync(() => languageProvider.GetLanguagesAsync());
return AsyncHelper.RunSync(languageProvider.GetLanguagesAsync);
}
}
}

1
framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj

@ -17,7 +17,6 @@
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.Data\Volo.Abp.Data.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
<ProjectReference Include="..\Volo.Abp.Settings\Volo.Abp.Settings.csproj" />
</ItemGroup>
</Project>

19
framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs

@ -1,20 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data;
using Volo.Abp.Data;
using Volo.Abp.Modularity;
using Volo.Abp.Settings;
namespace Volo.Abp.MultiTenancy
{
[DependsOn(typeof(AbpDataModule))]
[DependsOn(typeof(AbpSettingsModule))]
public class AbpMultiTenancyAbstractionsModule : AbpModule //TODO: Rename to AbpMultiTenancyModule
[DependsOn(
typeof(AbpDataModule)
)]
public class AbpMultiTenancyAbstractionsModule : AbpModule //TODO: Rename to AbpMultiTenancyModule?
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<SettingOptions>(options =>
{
options.ValueProviders.Add<TenantSettingValueProvider>();
});
}
}
}

45
framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantSettingValueProvider.cs

@ -1,45 +0,0 @@
using System.Threading.Tasks;
using Volo.Abp.Settings;
namespace Volo.Abp.MultiTenancy
{
public class TenantSettingValueProvider : SettingValueProvider
{
public const string ProviderName = "Tenant";
public override string Name => ProviderName;
protected ICurrentTenant CurrentTenant { get; }
public TenantSettingValueProvider(ISettingStore settingStore, ICurrentTenant currentTenant)
: base(settingStore)
{
CurrentTenant = currentTenant;
}
public override async Task<string> GetOrNullAsync(SettingDefinition setting, string providerKey)
{
return await SettingStore.GetOrNullAsync(setting.Name, Name, NormalizeProviderKey(providerKey));
}
public override Task SetAsync(SettingDefinition setting, string value, string providerKey)
{
return SettingStore.SetAsync(setting.Name, value, Name, NormalizeProviderKey(providerKey));
}
public override Task ClearAsync(SettingDefinition setting, string providerKey)
{
return SettingStore.DeleteAsync(setting.Name, Name, NormalizeProviderKey(providerKey));
}
private string NormalizeProviderKey(string providerKey)
{
if (providerKey == null && CurrentTenant.Id.HasValue)
{
return CurrentTenant.Id.Value.ToString();
}
return providerKey;
}
}
}

44
framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs

@ -21,6 +21,21 @@ namespace System.Security.Principal
return Guid.Parse(userIdOrNull.Value);
}
public static Guid? FindUserId([NotNull] this IIdentity identity)
{
Check.NotNull(identity, nameof(identity));
var claimsIdentity = identity as ClaimsIdentity;
var userIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.UserId);
if (userIdOrNull == null || userIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
return Guid.Parse(userIdOrNull.Value);
}
public static Guid? FindTenantId([NotNull] this ClaimsPrincipal principal)
{
Check.NotNull(principal, nameof(principal));
@ -34,34 +49,47 @@ namespace System.Security.Principal
return Guid.Parse(tenantIdOrNull.Value);
}
public static Guid? FindUserId([NotNull] this IIdentity identity)
public static Guid? FindTenantId([NotNull] this IIdentity identity)
{
Check.NotNull(identity, nameof(identity));
var claimsIdentity = identity as ClaimsIdentity;
var userIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.UserId);
if (userIdOrNull == null || userIdOrNull.Value.IsNullOrWhiteSpace())
var tenantIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId);
if (tenantIdOrNull == null || tenantIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
return Guid.Parse(userIdOrNull.Value);
return Guid.Parse(tenantIdOrNull.Value);
}
public static Guid? FindTenantId([NotNull] this IIdentity identity)
public static string FindClientId([NotNull] this ClaimsPrincipal principal)
{
Check.NotNull(principal, nameof(principal));
var clientIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ClientId);
if (clientIdOrNull == null || clientIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
return clientIdOrNull.Value;
}
public static string FindClientId([NotNull] this IIdentity identity)
{
Check.NotNull(identity, nameof(identity));
var claimsIdentity = identity as ClaimsIdentity;
var tenantIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId);
if (tenantIdOrNull == null || tenantIdOrNull.Value.IsNullOrWhiteSpace())
var clientIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ClientId);
if (clientIdOrNull == null || clientIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
return Guid.Parse(tenantIdOrNull.Value);
return clientIdOrNull.Value;
}
}
}

20
framework/src/Volo.Abp.Security/Volo/Abp/Clients/CurrentClient.cs

@ -0,0 +1,20 @@
using System.Security.Principal;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.Clients
{
public class CurrentClient : ICurrentClient, ITransientDependency
{
public virtual string Id => _principalAccessor.Principal?.FindClientId();
public virtual bool IsAuthenticated => Id != null;
private readonly ICurrentPrincipalAccessor _principalAccessor;
public CurrentClient(ICurrentPrincipalAccessor principalAccessor)
{
_principalAccessor = principalAccessor;
}
}
}

9
framework/src/Volo.Abp.Security/Volo/Abp/Clients/ICurrentClient.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.Clients
{
public interface ICurrentClient
{
string Id { get; }
bool IsAuthenticated { get; }
}
}

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

@ -47,5 +47,10 @@ namespace Volo.Abp.Security.Claims
/// Default: "phone_number_verified".
/// </summary>
public static string TenantId { get; set; } = "tenantid";
/// <summary>
/// Default: "client_id".
/// </summary>
public static string ClientId { get; set; } = "client_id";
}
}

1
framework/src/Volo.Abp.Settings/Volo.Abp.Settings.csproj

@ -15,6 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
</ItemGroup>

16
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs

@ -1,15 +1,27 @@
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security;
using Volo.Abp.Users;
namespace Volo.Abp.Settings
{
[DependsOn(
typeof(AbpLocalizationAbstractionsModule),
typeof(AbpSecurityModule)
typeof(AbpSecurityModule),
typeof(AbpMultiTenancyAbstractionsModule)
)]
public class AbpSettingsModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<SettingOptions>(options =>
{
options.ValueProviders.Add<DefaultValueSettingValueProvider>();
options.ValueProviders.Add<GlobalSettingValueProvider>();
options.ValueProviders.Add<TenantSettingValueProvider>();
options.ValueProviders.Add<UserSettingValueProvider>();
});
}
}
}

14
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/DefaultValueSettingValueProvider.cs

@ -14,21 +14,9 @@ namespace Volo.Abp.Settings
}
public override Task<string> GetOrNullAsync(SettingDefinition setting, string providerKey)
public override Task<string> GetOrNullAsync(SettingDefinition setting)
{
return Task.FromResult(setting.DefaultValue);
}
public override Task SetAsync(SettingDefinition setting, string value, string providerKey)
{
setting.DefaultValue = value;
return Task.CompletedTask;
}
public override Task ClearAsync(SettingDefinition setting, string providerKey)
{
setting.DefaultValue = null;
return Task.CompletedTask;
}
}
}

12
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/GlobalSettingValueProvider.cs

@ -13,19 +13,9 @@ namespace Volo.Abp.Settings
{
}
public override Task<string> GetOrNullAsync(SettingDefinition setting, string providerKey)
public override Task<string> GetOrNullAsync(SettingDefinition setting)
{
return SettingStore.GetOrNullAsync(setting.Name, Name, null);
}
public override Task SetAsync(SettingDefinition setting, string value, string providerKey)
{
return SettingStore.SetAsync(setting.Name, value, Name, null);
}
public override Task ClearAsync(SettingDefinition setting, string providerKey)
{
return SettingStore.DeleteAsync(setting.Name, Name, null);
}
}
}

2
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ISettingDefinitionManager.cs

@ -9,5 +9,7 @@ namespace Volo.Abp.Settings
SettingDefinition Get([NotNull] string name);
IReadOnlyList<SettingDefinition> GetAll();
SettingDefinition GetOrNull(string name);
}
}

13
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ISettingProvider.cs

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Settings
{
public interface ISettingProvider
{
Task<string> GetOrNullAsync([NotNull]string name);
Task<List<SettingValue>> GetAllAsync();
}
}

15
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ISettingStore.cs

@ -1,17 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Settings
{
public interface ISettingStore
{
Task<string> GetOrNullAsync([NotNull] string name, [CanBeNull] string providerName, [CanBeNull] string providerKey);
Task SetAsync([NotNull] string name, [NotNull] string value, [CanBeNull] string providerName, [CanBeNull] string providerKey);
Task<List<SettingValue>> GetListAsync([CanBeNull] string providerName, [CanBeNull] string providerKey);
Task DeleteAsync([NotNull] string name, [CanBeNull]string providerName, [CanBeNull]string providerKey);
Task<string> GetOrNullAsync(
[NotNull] string name,
[CanBeNull] string providerName,
[CanBeNull] string providerKey
);
}
}

8
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ISettingValueProvider.cs

@ -7,12 +7,6 @@ namespace Volo.Abp.Settings
{
string Name { get; }
//TODO: There is a bug here, because we are checking the same providerKey in all providers in a fallback system!
Task<string> GetOrNullAsync([NotNull] SettingDefinition setting, [CanBeNull] string providerKey);
Task SetAsync([NotNull] SettingDefinition setting, [NotNull] string value, [CanBeNull] string providerKey);
Task ClearAsync([NotNull] SettingDefinition setting, [CanBeNull] string providerKey);
Task<string> GetOrNullAsync([NotNull] SettingDefinition setting);
}
}

31
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs

@ -36,6 +36,12 @@ namespace Volo.Abp.Settings
/// </summary>
public bool IsVisibleToClients { get; set; }
/// <summary>
/// A list of allowed providers to get/set value of this setting.
/// An empty list indicates that all providers are allowed.
/// </summary>
public List<string> Providers { get; }
/// <summary>
/// Is this setting inherited from parent scopes.
/// Default: True.
@ -72,6 +78,31 @@ namespace Volo.Abp.Settings
IsEncrypted = isEncrypted;
Properties = new Dictionary<string, object>();
Providers = new List<string>();
}
/// <summary>
/// Sets a property in the <see cref="Properties"/> dictionary.
/// This is a shortcut for nested calls on this object.
/// </summary>
public virtual SettingDefinition WithProperty(string key, object value)
{
Properties[key] = value;
return this;
}
/// <summary>
/// Sets a property in the <see cref="Properties"/> dictionary.
/// This is a shortcut for nested calls on this object.
/// </summary>
public virtual SettingDefinition WithProviders(params string[] providers)
{
if (!providers.IsNullOrEmpty())
{
Providers.AddRange(providers);
}
return this;
}
}
}

31
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingManagerExtensions.cs

@ -1,31 +0,0 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Settings
{
public static class SettingManagerExtensions
{
public static async Task<bool> IsTrueAsync([NotNull] this ISettingManager settingManager, [NotNull] string name)
{
Check.NotNull(settingManager, nameof(settingManager));
Check.NotNull(name, nameof(name));
return string.Equals(
await settingManager.GetOrNullAsync(name),
"true",
StringComparison.OrdinalIgnoreCase
);
}
public static async Task<T> GetAsync<T>([NotNull] this ISettingManager settingManager, [NotNull] string name, T defaultValue = default)
where T : struct
{
Check.NotNull(settingManager, nameof(settingManager));
Check.NotNull(name, nameof(name));
var value = await settingManager.GetOrNullAsync(name);
return value?.To<T>() ?? defaultValue;
}
}
}

34
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingManagerSyncExtensions.cs

@ -1,34 +0,0 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Threading;
namespace Volo.Abp.Settings
{
public static class SettingManagerSyncExtensions
{
public static string GetOrNull([NotNull] this ISettingManager settingManager, [NotNull] string name)
{
Check.NotNull(settingManager, nameof(settingManager));
return AsyncHelper.RunSync(() => settingManager.GetOrNullAsync(name));
}
public static List<SettingValue> GetAll([NotNull] this ISettingManager settingManager)
{
Check.NotNull(settingManager, nameof(settingManager));
return AsyncHelper.RunSync(settingManager.GetAllAsync);
}
public static T Get<T>([NotNull] this ISettingManager settingManager, [NotNull] string name, T defaultValue = default)
where T : struct
{
return AsyncHelper.RunSync(() => settingManager.GetAsync(name, defaultValue));
}
public static bool IsTrue([NotNull] this ISettingManager settingManager, [NotNull] string name)
{
return AsyncHelper.RunSync(() => settingManager.IsTrueAsync(name));
}
}
}

6
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingOptions.cs

@ -11,11 +11,7 @@ namespace Volo.Abp.Settings
public SettingOptions()
{
DefinitionProviders = new TypeList<ISettingDefinitionProvider>();
ValueProviders = new TypeList<ISettingValueProvider>
{
typeof(DefaultValueSettingValueProvider),
typeof(GlobalSettingValueProvider)
};
ValueProviders = new TypeList<ISettingValueProvider>();
}
}
}

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

Loading…
Cancel
Save