Browse Source

Merge branch 'dev' into liangshiwei/rebus

pull/5637/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
c4ccb1fb82
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .github/ISSUE_TEMPLATES/feature.md
  2. 71
      .github/workflows/codeql-analysis.yml
  3. 1
      .gitignore
  4. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  5. 2
      docs/en/.vscode/settings.json
  6. 47
      docs/en/Blog-Posts/2020-10-01 v3_2_Release_Stable/POST.md
  7. 3
      docs/en/CLI.md
  8. 258
      docs/en/Caching.md
  9. 317
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/Using-DevExtremeAngularComponents-With-The-ABP-Framework.md
  10. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/dev-extreme-page.png
  11. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-final.gif
  12. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-first.png
  13. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-second.png
  14. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/localhost-running.png
  15. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/login-screen.png
  16. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/project-setup.png
  17. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users-on-dev-extreme.png
  18. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users.png
  19. 4
      docs/en/Distributed-Event-Bus-Kafka-Integration.md
  20. 12
      docs/en/Entity-Framework-Core-SQLite.md
  21. 2
      docs/en/Getting-Started-React-Native.md
  22. 28
      docs/en/Getting-Started.md
  23. 5
      docs/en/Localization.md
  24. 43
      docs/en/Redis-Cache.md
  25. 2
      docs/en/SignalR-Integration.md
  26. 4
      docs/en/Tutorials/Part-2.md
  27. 346
      docs/en/UI/Angular/PWA-Configuration.md
  28. 2
      docs/en/UI/Angular/Service-Proxies.md
  29. 2
      docs/en/UI/Angular/Toaster-Service.md
  30. BIN
      docs/en/UI/Angular/images/pwa-configuration-ng-add.png
  31. 206
      docs/en/UI/AspNetCore/Forms-Validation.md
  32. 206
      docs/en/UI/AspNetCore/Navigation-Menu.md
  33. 84
      docs/en/UI/AspNetCore/Page-Alerts.md
  34. 8
      docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md
  35. 6
      docs/en/UI/Blazor/Overall.md
  36. 3
      docs/en/UI/Blazor/Services/Notification.md
  37. 44
      docs/en/docs-nav.json
  38. BIN
      docs/en/images/abp-dynamic-form-result.png
  39. BIN
      docs/en/images/abp-form-input-validation-error-french-name.png
  40. BIN
      docs/en/images/abp-form-input-validation-error-french.png
  41. BIN
      docs/en/images/abp-form-input-validation-error.png
  42. BIN
      docs/en/images/nav-main-menu-administration.png
  43. BIN
      docs/en/images/nav-main-menu.png
  44. BIN
      docs/en/images/page-alert-account-layout.png
  45. BIN
      docs/en/images/page-alert-example.png
  46. BIN
      docs/en/images/user-menu.png
  47. 131
      framework/.editorconfig
  48. 6
      framework/Volo.Abp.sln
  49. 6
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicTheme.cs
  50. 20
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BasicThemeToolbarContributor.cs
  51. 7
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor
  52. 40
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs
  53. 9
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor
  54. 46
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor
  55. 55
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenuItem.razor
  56. 8
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavToolbar.razor
  57. 32
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavToolbar.razor.cs
  58. 31
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs
  59. 17
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/Extensions/DependencyInjection/AbpWebAssemblyServiceCollectionExtensions.cs
  60. 9
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
  61. 72
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/AbpExceptionHandlingLogger.cs
  62. 38
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/AbpExceptionHandlingLoggerProvider.cs
  63. 10
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/IUserExceptionInformer.cs
  64. 49
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs
  65. 16
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformerContext.cs
  66. 9
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs
  67. 13
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs
  68. 48
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs
  69. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperService.cs
  70. 19
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbItemTagHelperService.cs
  71. 32
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs
  72. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonTagHelper.cs
  73. 16
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonTagHelperService.cs
  74. 11
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonTagHelperServiceBase.cs
  75. 32
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Carousel/AbpCarouselItemTagHelperService.cs
  76. 97
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Carousel/AbpCarouselTagHelperService.cs
  77. 50
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs
  78. 35
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionTagHelperService.cs
  79. 45
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs
  80. 42
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs
  81. 45
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs
  82. 162
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs
  83. 38
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalHeaderTagHelperService.cs
  84. 87
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalTagHelperService.cs
  85. 30
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Nav/AbpNavbarToggleTagHelperService.cs
  86. 16
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs
  87. 58
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs
  88. 35
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabDropdownTagHelperService.cs
  89. 33
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs
  90. 74
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabTagHelperService.cs
  91. 46
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabsTagHelperService.cs
  92. 23
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/TagBuilderExtensions.cs
  93. 18
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tooltip/AbpTooltipTagHelperService.cs
  94. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs
  95. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  96. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js
  97. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs
  98. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs
  99. 4
      framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj
  100. 12
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs

1
.github/ISSUE_TEMPLATES/feature.md

@ -0,0 +1 @@
Please describe the feature need!

71
.github/workflows/codeql-analysis.yml

@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [dev, rel-1.0, rel-1.1, rel-2.0, rel-2.1, rel-2.2, rel-2.3, rel-2.4, rel-2.5, rel-2.6, rel-2.7, rel-2.8, rel-2.9, rel-3.0, rel-3.1, rel-3.2]
pull_request:
# The branches below must be a subset of the branches above
branches: [dev]
schedule:
- cron: '0 8 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['csharp', 'javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

1
.gitignore

@ -304,3 +304,4 @@ modules/virtual-file-explorer/app/Volo.Abp.VirtualFileExplorer.DemoApp/Logs/
/templates/app/angular/package-lock.json /templates/app/angular/package-lock.json
/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package-lock.json /modules/client-simulation/demo/Volo.ClientSimulation.Demo/package-lock.json
abp-build-config.json abp-build-config.json
/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Pages/Test/

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -83,6 +83,7 @@
"LatestBlogPost": "Latest Blog Post", "LatestBlogPost": "Latest Blog Post",
"Edit": "Edit", "Edit": "Edit",
"ProfileImageChange": "Change the profile image", "ProfileImageChange": "Change the profile image",
"BlogItemErrorMessage": "Could not get the latest blog post details from ABP." "BlogItemErrorMessage": "Could not get the latest blog post details from ABP.",
"PlannedReleaseDate": "Planned release date"
} }
} }

2
docs/en/.vscode/settings.json

@ -7,7 +7,9 @@
"formatter", "formatter",
"md", "md",
"monorepo", "monorepo",
"ngsw",
"npx", "npx",
"pwa",
"rootNamespace" "rootNamespace"
] ]
} }

47
docs/en/Blog-Posts/2020-10-01 v3_2_Release_Stable/POST.md

@ -0,0 +1,47 @@
# ABP Framework 3.2 Final Has Been Released
ABP Framework & ABP Commercial 3.2 have been released today.
Since all the new features are already explained in details with the [3.2 RC Announcement Post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI), I will not repeat all the details again. Please read [the RC post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI) for **new feature and changes** you may need to do for your solution while upgrading to the version 3.2.
## Creating New Solutions
You can create a new solution with the ABP Framework version 3.2 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started).
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details.
## How to Upgrade an Existing Solution
### Install/Update the ABP CLI
First of all, install the ABP CLI or upgrade to the latest version.
If you haven't installed yet:
````bash
dotnet tool install -g Volo.Abp.Cli
````
To update an existing installation:
```bash
dotnet tool update -g Volo.Abp.Cli
```
### ABP UPDATE Command
[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command:
````bash
abp update
````
After the update command, check [the RC blog post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI) to learn if you need to make any changes in your solution.
> You may want to see the new [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading).
## About the Next Versions
The next two versions (3.3 & 4.0) will be mostly related to completing the Blazor UI features and upgrading the ABP Framework & ecosystem to the .NET 5.0.
The ultimate goal is to complete the version 4.0 with a stable Blazor UI with the fundamental features implemented and publish it just after the Microsoft lunches .NET 5 in this November.

3
docs/en/CLI.md

@ -180,7 +180,8 @@ abp add-module Volo.Blogging
* `--solution` or `-s`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory. * `--solution` or `-s`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory.
* `--skip-db-migrations`: For EF Core database provider, it automatically adds a new code first migration (`Add-Migration`) and updates the database (`Update-Database`) if necessary. Specify this option to skip this operation. * `--skip-db-migrations`: For EF Core database provider, it automatically adds a new code first migration (`Add-Migration`) and updates the database (`Update-Database`) if necessary. Specify this option to skip this operation.
* `-sp` or `--startup-project`: Relative path to the project folder of the startup project. Default value is the current folder. * `-sp` or `--startup-project`: Relative path to the project folder of the startup project. Default value is the current folder.
* `--with-source-code`: Add source code of the module instead of NuGet/NPM packages. * `--with-source-code`: Downloads the source code of the module to your solution folder and uses local project references instead of NuGet/NPM packages.
* `--add-to-solution-file`: Adds the downloaded module to your solution file, so you will also see the projects of the module when you open the solution on a IDE. (only available when `--with-source-code` is used.)
### get-source ### get-source

258
docs/en/Caching.md

@ -1,154 +1,167 @@
# Caching # Caching
ABP framework extends ASP.NET Core's distributed caching system. ABP Framework extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed).
## Volo.Abp.Caching Package ## Installation
> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. > This package is already installed by default with the [application startup template](Startup-Templates/Application.md). So, most of the time, you don't need to install it manually.
Volo.Abp.Caching is the core package of the caching system. Install it to your project using the package manager console (PMC): [Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching) is the main package of the caching system. You can install it a project using the add-package command of the [ABP CLI](CLI.md):
``` ```
Install-Package Volo.Abp.Caching abp add-package Volo.Abp.Caching
``` ```
Then you can add **AbpCachingModule** dependency to your module: You need to run this command on a command line terminal in a folder containing a `csproj` file (see [other options](https://abp.io/package-detail/Volo.Abp.Caching) to install).
```c# ## Usage
using Volo.Abp.Modularity;
using Volo.Abp.Caching;
namespace MyCompany.MyProject ### `IDistributedCache` Interface
{
[DependsOn(typeof(AbpCachingModule))]
public class MyModule : AbpModule
{
//...
}
}
```
## `IDistributedCache` Interface
ASP.NET Core defines the `IDistributedCache` interface to get/set cache values. But it has some difficulties: ASP.NET Core defines the `IDistributedCache` interface to get/set the cache values. But it has some difficulties:
* It works with **byte arrays** rather than .NET objects. So, you need to **serialize/deserialize** the objects you need to cache. * It works with **byte arrays** rather than .NET objects. So, you need to **serialize/deserialize** the objects you need to cache.
* It provides a **single key pool** for all cache items, so; * It provides a **single key pool** for all cache items, so;
* You need to care about the keys to distinguish **different type of objects**. * You need to care about the keys to distinguish **different type of objects**.
* You need to care about the cache items of **different tenants** (see [multi-tenancy](Multi-Tenancy.md)). * You need to care about the cache items of **different tenants** in a [multi-tenant](Multi-Tenancy.md) system.
> `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**. > `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**.
> Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works **in-memory**. See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to **Redis** or another cache provider. > Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works **in-memory**. See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use the Redis as the distributed cache server.
See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information. See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information.
## `IDistributedCache<TCacheItem>` Interface ### `IDistributedCache<TCacheItem>` Interface
ABP framework defines the generic `IDistributedCache<TCacheItem>` interface in the [Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching/) package. `TCacheItem` is the type of the object stored in the cache. ABP framework defines the generic `IDistributedCache<TCacheItem>` interface in the [Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching/) package. `TCacheItem` is the type of the object stored in the cache.
`IDistributedCache<TCacheItem>` solves the difficulties explained above; `IDistributedCache<TCacheItem>` solves the difficulties explained above;
* It internally **serializes/deserializes** the cached objects. Uses **JSON** serialization by default, but can be overridden by replacing the `IDistributedCacheSerializer` service in the [dependency injection](Dependency-Injection.md) system. * It internally **serializes/deserializes** the cached objects. Uses **JSON** serialization by default, but can be overridden by replacing the `IDistributedCacheSerializer` service in the [dependency injection](Dependency-Injection.md) system.
* It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. Default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the `CacheName` attribute on the cache item class to set the cache name. * It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. Default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the **`CacheName` attribute** on the cache item class to set the cache name.
* It automatically adds the **current tenant id** to the cache key to distinguish cache items for different tenants (only works if your application is [multi-tenant](Multi-Tenancy.md)). Define `IgnoreMultiTenancy` attribute on the cache item class to disable this if you want to share the cached objects among all tenants in a multi-tenant application. * It automatically adds the **current tenant id** to the cache key to distinguish cache items for different tenants (if your application is [multi-tenant](Multi-Tenancy.md)). Define `IgnoreMultiTenancy` attribute on the cache item class to disable this if you want to share the cached objects among all tenants in a multi-tenant application.
* Allows to define a **global cache key prefix** per application, so different applications can use their isolated key pools in a shared distributed cache source. * Allows to define a **global cache key prefix** per application, so different applications can use their isolated key pools in a shared distributed cache server.
* It **can tolerate errors** wherever possible and bypasses the cache. This is useful when you have temporary problems on the cache server.
* It has methods like `GetManyAsync` and `SetManyAsync` which significantly improve the performance on **batch operations**.
### Usage **Example: Store Book names and prices in the cache**
An example class to store an item in the cache:
````csharp ````csharp
public class BookCacheItem namespace MyProject
{ {
public string Name { get; set; } public class BookCacheItem
{
public string Name { get; set; }
public float Price { get; set; } public float Price { get; set; }
}
} }
```` ````
You can inject and use the `IDistributedCache<BookCacheItem>` service to get/set `BookCacheItem` objects. You can inject and use the `IDistributedCache<BookCacheItem>` service to get/set `BookCacheItem` objects:
Example usage:
````csharp ````csharp
public class BookService : ITransientDependency using System;
{ using System.Threading.Tasks;
private readonly IDistributedCache<BookCacheItem> _cache; using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
public BookService(IDistributedCache<BookCacheItem> cache) using Volo.Abp.DependencyInjection;
{
_cache = cache;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId.ToString(), //Cache key
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId) namespace MyProject
{
public class BookService : ITransientDependency
{ {
//TODO: get from database private readonly IDistributedCache<BookCacheItem> _cache;
public BookService(IDistributedCache<BookCacheItem> cache)
{
_cache = cache;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId.ToString(), //Cache key
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
{
//TODO: get from database
}
} }
} }
```` ````
* This sample service uses the `GetOrAddAsync()` method to get a book item from the cache. * This sample service uses the `GetOrAddAsync()` method to get a book item from the cache. `GetOrAddAsync` is an additional method that was added by the ABP Framework to the standard ASP.NET Core distributed cache methods.
* If the book was not found in the cache, it calls the factory method (`GetBookFromDatabaseAsync` in this case) to retrieve the book item from the original source. * If the book was not found in the cache, it calls the factory method (`GetBookFromDatabaseAsync` in this case) to retrieve the book item from the original source.
* `GetOrAddAsync` optionally gets a `DistributedCacheEntryOptions` which can be used to set the lifetime of the cached item. * `GetOrAddAsync` optionally gets a `DistributedCacheEntryOptions` which can be used to set the lifetime of the cached item.
Other methods of the `IDistributedCache<BookCacheItem>` are same as ASP.NET Core's `IDistributedCache` interface, so you can refer [it's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). `IDistributedCache<BookCacheItem>` supports the same methods of the ASP.NET Core's standard `IDistributedCache` interface, so you can refer [it's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed).
## `IDistributedCache<TCacheItem, TCacheKey>` Interface ### `IDistributedCache<TCacheItem, TCacheKey>` Interface
`IDistributedCache<TCacheItem>` interface assumes that the type of your cache key is `string` (so, you need to manually convert your key to string if you need to use a different kind of cache key). `IDistributedCache<TCacheItem, TCacheKey>` can be used when your cache key type is not `string`. `IDistributedCache<TCacheItem>` interface assumes that the type of your **cache key** is `string` (so, you need to manually convert your key to string if you need to use a different kind of cache key). While this is not a big deal, `IDistributedCache<TCacheItem, TCacheKey>` can be used when your cache key type is not `string`.
### Usage **Example: Store Book names and prices in the cache**
An example class to store an item in the cache:
````csharp ````csharp
public class BookCacheItem using Volo.Abp.Caching;
namespace MyProject
{ {
public string Name { get; set; } [CacheName("Books")]
public class BookCacheItem
{
public string Name { get; set; }
public float Price { get; set; } public float Price { get; set; }
}
} }
```` ````
Example usage (assumes that your cache key type is `Guid`): * This example uses the `CacheName` attribute for the `BookCacheItem` class to set the cache name.
````csharp You can inject and use the `IDistributedCache<BookCacheItem, Guid>` service to get/set `BookCacheItem` objects:
public class BookService : ITransientDependency
{
private readonly IDistributedCache<BookCacheItem, Guid> _cache;
public BookService(IDistributedCache<BookCacheItem, Guid> cache) ````csharp
{ using System;
_cache = cache; using System.Threading.Tasks;
} using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
public async Task<BookCacheItem> GetAsync(Guid bookId) namespace MyProject
{ {
return await _cache.GetOrAddAsync( public class BookService : ITransientDependency
bookId, //Guid type used as the cache key
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
{ {
//TODO: get from database private readonly IDistributedCache<BookCacheItem, Guid> _cache;
public BookService(IDistributedCache<BookCacheItem, Guid> cache)
{
_cache = cache;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId, //Guid type used as the cache key
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
{
//TODO: get from database
}
} }
} }
```` ````
@ -156,6 +169,8 @@ public class BookService : ITransientDependency
* This sample service uses the `GetOrAddAsync()` method to get a book item from the cache. * This sample service uses the `GetOrAddAsync()` method to get a book item from the cache.
* Since cache explicitly implemented as using `Guid` as cache key, `Guid` value passed to `_cache_GetOrAddAsync()` method. * Since cache explicitly implemented as using `Guid` as cache key, `Guid` value passed to `_cache_GetOrAddAsync()` method.
#### Complex Types as the Cache Key
`IDistributedCache<TCacheItem, TCacheKey>` internally uses `ToString()` method of the key object to convert it to a string. If you need to use a complex object as the cache key, you need to override `ToString` method of your class. `IDistributedCache<TCacheItem, TCacheKey>` internally uses `ToString()` method of the key object to convert it to a string. If you need to use a complex object as the cache key, you need to override `ToString` method of your class.
An example class that is used as a cache key: An example class that is used as a cache key:
@ -192,7 +207,66 @@ public class BookService : ITransientDependency
} }
```` ````
### DistributedCacheOptions ## Configuration
### AbpDistributedCacheOptions
`AbpDistributedCacheOptions` is the main [options class](Options.md) to configure the caching.
**Example: Set the cache key prefix for the application**
```csharp
Configure<AbpDistributedCacheOptions>(options =>
{
options.KeyPrefix = "MyApp1";
});
```
> Write that code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md).
#### Available Options
* `HideErrors` (`bool`, default: `true`): Enables/disables hiding the errors on writing/reading values from the cache server.
* `KeyPrefix` (`string`, default: `null`): If your cache server is shared by multiple applications, you can set a prefix for the cache keys for your application. In this case, different applications can not overwrite each other's cache items.
* `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): Used to set default distributed cache options (like `AbsoluteExpiration` and `SlidingExpiration`) used when you don't specify the options while saving cache items. Default value uses the `SlidingExpiration` as 20 minutes.
## Error Handling
When you design a cache for your objects, you typically try to get the value from cache first. If not found in the cache, you query the object from the **original source**. It may be located in a **database** or may require to perform an HTTP call to a remote server.
In most cases, you want to **tolerate the cache errors**; If you get error from the cache server you don't want to cancel the operation. Instead, you silently hide (and log) the error and **query from the original source**. This is what the ABP Framework does by default.
ABP's Distributed Cache [handle](Exception-Handling.md), log and hide errors by default. There is an option to change this globally (see the options below).
In addition, all of the `IDistributedCache<TCacheItem>` (and `IDistributedCache<TCacheItem, TCacheKey>`) methods have an optional `hideErrors` parameter, which is `null` by default. The global value is used if this parameter left as `null`, otherwise you can decide to hide or throw the exceptions for individual method calls.
## Batch Operations
ABP's distributed cache interfaces provide methods to perform batch get/set methods those improves the performance when you want to get or set multiple cache items in a single method call.
* `SetManyAsync` and `SetMany` methods can be used to set multiple values to the cache.
* `GetManyAsync` and `GetMany` methods can be used to retrieve multiple values from the cache.
> These are not standard methods of the ASP.NET Core caching. So, some providers may not support them. They are supported by the [ABP Redis Cache integration package](Redis-Cache.md). If the provider doesn't support, it fallbacks to `SetAsync` and `GetAsync` methods (called once for each item).
## Advanced Topics
### Unit Of Work Level Cache
Distributed cache service provides an interesting feature. Assume that you've updated the price of a book in the database, then set the new price to the cache, so you can use the cached value later. What if you have an exception after setting the cache and you **rollback the transaction** that updates the price of the book? In this case, cache value will be incorrect.
`IDistributedCache<..>` methods gets an optional parameter, named `considerOuw`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](Unit-Of-Work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeed**.
### IDistributedCacheSerializer
`IDistributedCacheSerializer` service is used to serialize and deserialize the cache items. Default implementation is the `Utf8JsonDistributedCacheSerializer` class that uses `IJsonSerializer` service to convert objects to [JSON](Json.md) and vice verse. Then it uses UTC8 encoding to convert the JSON string to a byte array which is accepted by the distributed cache.
You can [replace](Dependency-Injection.md) this service by your own implementation if you want to implement your own serialization logic.
### IDistributedCacheKeyNormalizer
`IDistributedCacheKeyNormalizer` is implemented by the `DistributedCacheKeyNormalizer` class by default. It adds cache name, application cache prefix and current tenant id to the cache key. If you need a more advanced key normalization, you can [replace](Dependency-Injection.md) this service by your own implementation.
TODO ## See Also
* [Redis Cache](Redis-Cache.md)

317
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/Using-DevExtremeAngularComponents-With-The-ABP-Framework.md

@ -0,0 +1,317 @@
# Using DevExtreme Angular Components With the ABP Framework
In this article, I will show you how to integrate the [DevExpress Angular components](https://js.devexpress.com/Documentation/Guide/Angular_Components/DevExtreme_Angular_Components/) to a project created using the ABP Framework startup templates. Then I will use the [DataGrid](https://js.devexpress.com/Documentation/Guide/Widgets/DataGrid/Overview/) component to show a list of users on the UI.
## Create the Project
Let's create a new web application with the Angular UI using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI#new):
```shell
abp new DevExtremeAngular -u angular
```
> For detail information about how to generate and start up a project, please refer to the [official docs](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No). For the scope of this post, we will not go into details of the backend applications.
## Running the Solution
### The Server Side
Server side contains multiple projects in the solution:
![A screenshot showing DevExtremeAngular.DbMigrator and DevExtremeAngular.HttpApi.Host](project-setup.png)
Run following projects in order;
* Run `DevExtremeAngular.DbMigrator` to create the database and seed the initial data.
* Run `DevExtremeAngular.HttpApi.Host` project to make the backend API up & running.
### The Angular Application
Open a command line terminal and navigate to `angular` folder then run `yarn` or `npm install` based on which package you are using.
After installation process is done, you can start your angular project by running `yarn start` or `npm start`. This command should serve the application and open the application in your default browser. If it doesn't open, you can navigate to http://localhost:4200 in your browser:
![A screenshot showing localhost is running](localhost-running.png)
You can login to the application by using following credentials:
> _Default admin username is **admin** and password is **1q2w3E\***_
![A screenshot showing login page](login-screen.png)
After successful login, you should be redirected to home page.
## Install DevExtreme
You can follow [the guide](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Add_DevExtreme_to_an_Angular_CLI_Application/) provided by **DevExtreme** team or apply the following steps.
* `npm install devextreme devextreme-angular` or `yarn add devextreme devextreme-angular`
* Import given two following styles in `angular.json` file:
```javascript
// ...
"styles": [
// ...
"src/styles.scss",
"node_modules/devextreme/dist/css/dx.common.css",
"node_modules/devextreme/dist/css/dx.light.css"
]
```
* Add `dx-viewport` to classes of `body` in `index.html`
```html
<body class="bg-light dx-viewport">
<app-root>
<div class="donut centered"></div>
</app-root>
</body>
```
After completing these steps, you need to restart the angular application.
## Create a lazy Angular Module for DevExtreme Demo
Let's create a module which will be loaded lazily.
Open up a terminal and navigate to `angular` to run following command.
```shell
ng g m dev-extreme --route dev-extreme --module app
```
...or with `npx`, if you do not have `angular-cli` installed...
```shell
npx ng g m dev-extreme --route dev-extreme --module app
```
Your terminal should log the following output:
```shell
CREATE src/app/dev-extreme/dev-extreme-routing.module.ts (361 bytes)
CREATE src/app/dev-extreme/dev-extreme.module.ts (379 bytes)
CREATE src/app/dev-extreme/dev-extreme.component.scss (0 bytes)
CREATE src/app/dev-extreme/dev-extreme.component.html (26 bytes)
CREATE src/app/dev-extreme/dev-extreme.component.spec.ts (655 bytes)
CREATE src/app/dev-extreme/dev-extreme.component.ts (295 bytes)
UPDATE src/app/app-routing.module.ts (362 bytes)
```
The Angular CLI has created a module and configured it to lazy-load at `/dev-extreme` path.
The last step to be able to see our newly created module in the browser, open `route.provider.ts` and edit the array being added into the routes.
```typescript
// ...
routes.add([
{
path: '/',
name: '::Menu:Home',
iconClass: 'fas fa-home',
order: 1,
layout: eLayoutType.application,
},
{
path: '/dev-extreme',
name: 'Dev Extreme',
order: 2,
layout: eLayoutType.application,
},
]);
// ...
```
After completing the steps above, you should be able to see `Dev Extreme` on the header and when you click on it, you should be redirected to `/dev-extreme` page and see the following message on the screen.
![A screenshot showing a page that says dev-extreme works!](dev-extreme-page.png)
## Display users on the dev-extreme page
For this demo, we will list users on the screen. We already have `admin` as our first user.
Let's add couple of more to the list in `Administration -> Identity Management -> Users` page.
![A screenshot showing users page after adding couple of users](users.png)
Now we are ready to fetch our users and display them on `/dev-extreme` page.
Firstly, let's create a service for our component.
Navigate to the `dev-extreme` folder and run following command. If you run this command at the root, the service will be generated next to `app.module.ts`
```shell
ng g s dev-extreme
```
Following files should be created
```shell
CREATE src/app/dev-extreme/dev-extreme.service.spec.ts (378 bytes)
CREATE src/app/dev-extreme/dev-extreme.service.ts (139 bytes)
```
Let's import and inject `IdentityService` as dependency in `dev-extreme.service.ts`. After then, let's create a stream called `users$` to retrieve the users.
`identityService.getUsers` returns `ABP.PagedResponse` which contains two fields, `items` and `totalCount`. We are only interested in `items` for now.
When we apply the steps described above, the final version of `dev-extreme.service` should be as follows
```typescript
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';
import { IdentityService } from '@abp/ng.identity';
@Injectable({
providedIn: 'root',
})
export class DevExtremeService {
users$ = this.service.getUsers().pipe(map((result) => result.items));
constructor(private service: IdentityService) {}
}
```
Now we can simply inject `DevExtremeService` as public and utilize `users$` stream in `dev-extreme.component.ts` as follows:
```typescript
import { Component } from '@angular/core';
import { DevExtremeService } from './dev-extreme.service';
@Component({
selector: 'app-dev-extreme',
templateUrl: './dev-extreme.component.html',
styleUrls: ['./dev-extreme.component.scss'],
})
export class DevExtremeComponent {
constructor(public service: DevExtremeService) {}
}
```
And use it within `dev-extreme.component.html`
```html
<ng-container *ngIf="service.users$ | async as users">
<ul>
<li *ngFor="let user of users">
{{ user.name }}
</li>
</ul>
</ng-container>
```
This should list names of the users on the screen
![A screenshot showing list users on the dev extreme page](users-on-dev-extreme.png)
## Use DxDataGrid to list the users
You can take a look at [demo](https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/ColumnCustomization/Angular/Light/) provided by **DevExtreme** team or apply the following steps.
Now, our application is ready to use `dx-data-grid` in `dev-extreme.component.ts`
Firstly, we need to import `DxDataGridModule` in our module as follows.
```typescript
// ...
import { DxDataGridModule } from 'devextreme-angular';
@NgModule({
// ...
imports: [
// ...
DxDataGridModule
],
})
export class DevExtremeModule {}
```
At this point `dx-data-grid` is avaliable within our module and we can use it in our template.
Change `dev-extreme.component.html` to the following
```html
<ng-container *ngIf="service.users$ | async as users">
<dx-data-grid [dataSource]="users"></dx-data-grid>
</ng-container>
```
It should display a table on the screen
![A screenshot displaying dev extreme data grid with lots of columns](devextreme-first.png)
Since, we did not specify any columns, `dx-data-grid` displayed every column avaliable. Let's pick some columns to make it more readable.
Change `dev-extreme.component.html` to the following:
```html
<ng-container *ngIf="service.users$ | async as users">
<dx-data-grid [dataSource]="users">
<dxi-column dataField="userName"></dxi-column>
<dxi-column dataField="name"></dxi-column>
<dxi-column dataField="surname"></dxi-column>
<dxi-column dataField="email"></dxi-column>
<dxi-column dataField="phoneNumber"></dxi-column>
</dx-data-grid>
</ng-container>
```
which will display following table on the screen
![A screenshot displaying dev extreme data grid with these columns: username, name, surname, email and phone number](devextreme-second.png)
We can also utilize `abpLocalization` pipe to translate the headers of the table. To use `abpLocalization` pipe in our templates, we need to import `CoreModule` from `@abp/ng.core` into our module.
```typescript
import { CoreModule } from '@abp/ng.core';
@NgModule({
// ...
imports: [
// ...
CoreModule
],
})
export class DevExtremeModule {}
```
And change the template to the following:
```html
<ng-container *ngIf="service.users$ | async as users">
<dx-data-grid [dataSource]="users">
<dxi-column
dataField="userName"
[caption]="'AbpIdentity::DisplayName:UserName' | abpLocalization"
></dxi-column>
<dxi-column
dataField="name"
[caption]="'AbpIdentity::DisplayName:Name' | abpLocalization"
></dxi-column>
<dxi-column
dataField="surname"
[caption]="'AbpIdentity::DisplayName:Surname' | abpLocalization"
></dxi-column>
<dxi-column
dataField="email"
[caption]="'AbpIdentity::DisplayName:Email' | abpLocalization"
></dxi-column>
<dxi-column
dataField="phoneNumber"
[caption]="'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization"
></dxi-column>
</dx-data-grid>
</ng-container>
```
The headers should change when a new language is selected;
![A gif showing the headers of the table getting translated into the chosen language](devextreme-final.gif)
## Conclusion
In this article, we have seen how to integrate `DevExtreme` angular components into a project generated by `ABP CLI`.
You can download source code of [the demo here](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Angular).

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/dev-extreme-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-final.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-first.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-second.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/localhost-running.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/login-screen.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/project-setup.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users-on-dev-extreme.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

4
docs/en/Distributed-Event-Bus-Kafka-Integration.md

@ -89,7 +89,7 @@ Defining multiple connections is allowed. In this case, you can specify the conn
} }
```` ````
This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus. This allows you to use multiple Kafka cluster in your application, but select one of them for the event bus.
You can use any of the [ClientConfig](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.ClientConfig.html) properties as the connection properties. You can use any of the [ClientConfig](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.ClientConfig.html) properties as the connection properties.
@ -164,4 +164,4 @@ Configure<AbpKafkaOptions>(options =>
}); });
```` ````
Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file.

12
docs/en/Entity-Framework-Core-SQLite.md

@ -23,6 +23,16 @@ Find `UseSqlServer()` calls in your solution, replace with `UseSqlite()`. Check
SQLite connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/sqlite/ ) for details of SQLite connection string options. SQLite connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/sqlite/ ) for details of SQLite connection string options.
An example connection string is
```
{
"ConnectionStrings": {
"Default": "Filename=./MySQLiteDBFile.sqlite"
}
}
```
You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure.
## Re-Generate the Migrations ## Re-Generate the Migrations
@ -38,4 +48,4 @@ Run the `.DbMigrator` project to create the database and seed the initial data.
## Run the Application ## Run the Application
It is ready. Just run the application and enjoy coding. It is ready. Just run the application and enjoy coding.

2
docs/en/Getting-Started-React-Native.md

@ -110,4 +110,4 @@ The application is up and running. You can continue to develop your application
## See Also ## See Also
* [React Native project structure](./Startup-Templates/Application.md#react-native) * [React Native project structure](./Startup-Templates/Application#react-native)

28
docs/en/Getting-Started.md

@ -43,24 +43,6 @@ The following tools should be installed on your development machine:
> ABP CLI is a free & open source tool for the ABP framework. > ABP CLI is a free & open source tool for the ABP framework.
{{if UI == "Blazor"}}
BLAZOR UI IS ONLY AVAILABLE WITH THE PREVIEW VERSION. SO, YOU NEED TO INSTALL THE PREVIEW VERSION OF THE ABP CLI
First, you need to install the ABP CLI **RC.2** using the following command:
````shell
dotnet tool install Volo.Abp.Cli -g --version 3.2.0-rc.2
````
If you've already installed, you can update it using the following command:
````shell
dotnet tool update Volo.Abp.Cli -g --version 3.2.0-rc.2
````
{{else}}
First, you need to install the ABP CLI using the following command: First, you need to install the ABP CLI using the following command:
````shell ````shell
@ -73,22 +55,14 @@ If you've already installed, you can update it using the following command:
dotnet tool update -g Volo.Abp.Cli dotnet tool update -g Volo.Abp.Cli
```` ````
{{end}}
## Create a New Project ## Create a New Project
Use the `new` command of the ABP CLI to create a new project: Use the `new` command of the ABP CLI to create a new project:
````shell ````shell
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor --preview{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}} abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}}
```` ````
{{if UI == "Blazor"}}
> BLAZOR UI IS ONLY AVAILABLE WITH THE PREVIEW VERSION. THIS IS WHY IT IS REQUIRED TO SET THE `--PREVIEW` OPTION.
{{end}}
> You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore. > You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.
> Alternatively, you can select the "Direct Download" tab from the [ABP Framework web site](https://abp.io/get-started) to create a new solution. > Alternatively, you can select the "Direct Download" tab from the [ABP Framework web site](https://abp.io/get-started) to create a new solution.

5
docs/en/Localization.md

@ -2,7 +2,7 @@
ABP's localization system is seamlessly integrated to the `Microsoft.Extensions.Localization` package and compatible with the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization). It adds some useful features and enhancements to make it easier to use in real life application scenarios. ABP's localization system is seamlessly integrated to the `Microsoft.Extensions.Localization` package and compatible with the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization). It adds some useful features and enhancements to make it easier to use in real life application scenarios.
## Volo.Abp.Localization Package ## Installation
> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. > This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually.
@ -242,4 +242,5 @@ Both of the samples above produce the output `Hello John, welcome!`.
## See Also ## See Also
* [Localization in Angular UI](UI/Angular/Localization.md) * [Localization in Angular UI](UI/Angular/Localization.md)
* [Forms & Validation](UI/AspNetCore/Forms-Validation.md) for the ASP.NET Core MVC / Razor Pages UI

43
docs/en/Redis-Cache.md

@ -0,0 +1,43 @@
# Redis Cache
ABP Framework [Caching System](Caching.md) extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). So, **any provider** supported by the standard ASP.NET Core distributed cache can be usable in your application and can be configured just like **documented by Microsoft**.
However, ABP provides an **integration package** for Redis Cache: [Volo.Abp.Caching.StackExchangeRedis](https://www.nuget.org/packages/Volo.Abp.Caching.StackExchangeRedis). There are two reasons for using this package, instead of the standard [Microsoft.Extensions.Caching.StackExchangeRedis](https://www.nuget.org/packages/Microsoft.Extensions.Caching.StackExchangeRedis/) package.
1. It implements `SetManyAsync` and `GetManyAsync` methods. These are not standard methods of the Microsoft Caching library, but added by the ABP Framework [Caching](Caching.md) system. They **significiantly increases the performance** when you need to set/get multiple cache items with a single method call.
2. It **simplifies** the Redis cache **configuration** (will be explained below).
> Volo.Abp.Caching.StackExchangeRedis is already uses the Microsoft.Extensions.Caching.StackExchangeRedis package, but extends and improves it.
## Installation
> This package is already installed in the application startup template if it is using Redis.
Open a command line in the folder of your `.csproj` file and type the following ABP CLI command:
````bash
abp add-package Volo.Abp.Caching.StackExchangeRedis
````
## Configuration
Volo.Abp.Caching.StackExchangeRedis package automatically gets the redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`:
````js
"Redis": {
"Configuration": "127.0.0.1"
}
````
Alternatively you can configure the standard [RedisCacheOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.stackexchangeredis.rediscacheoptions) [options](Options.md) class in the `ConfigureServices` method of your [module](Module-Development-Basics.md):
````csharp
Configure<RedisCacheOptions>(options =>
{
//...
});
````
## See Also
* [Caching](Caching.md)

2
docs/en/SignalR-Integration.md

@ -1,6 +1,6 @@
# SignalR Integration # SignalR Integration
> It is already possible to follow [the standard Microsoft tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr) to add [SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) to your application. However, ABP provides a SignalR integration packages those simplify the integration and usage. > It is already possible to follow [the standard Microsoft tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr) to add [SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) to your application. However, ABP provides SignalR integration packages those simplify the integration and usage.
## Installation ## Installation

4
docs/en/Tutorials/Part-2.md

@ -443,7 +443,7 @@ function configureRoutes(routes: RoutesService) {
* `order` is the order of the menu item. * `order` is the order of the menu item.
* `layout` is the layout of the BooksModule's routes (there are three types of pre-defined layouts: `eLayoutType.application`, `eLayoutType.account` or `eLayoutType.empty`). * `layout` is the layout of the BooksModule's routes (there are three types of pre-defined layouts: `eLayoutType.application`, `eLayoutType.account` or `eLayoutType.empty`).
For more information, see the [RoutesService document](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). For more information, see the [RoutesService document](../UI/Angular/Modifying-the-Menu.md#via-routesservice).
### Service Proxy Generation ### Service Proxy Generation
@ -643,7 +643,7 @@ Open the `Books.razor` and replace the content as the following:
</Card> </Card>
```` ````
> If you see some syntax errors, you can ignore them if your application property built and run. Visual Studio still has some bugs with Blazor. > If you see some syntax errors, you can ignore them if your application properly built and run. Visual Studio still has some bugs with Blazor.
* Inherited from the `BlazoriseCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>` which implements all the CRUD details for us. * Inherited from the `BlazoriseCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>` which implements all the CRUD details for us.
* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base blass. * `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base blass.

346
docs/en/UI/Angular/PWA-Configuration.md

@ -0,0 +1,346 @@
# PWA Configuration
[Progressive Web Apps](https://web.dev/progressive-web-apps/) are web applications which, although not as integrated to the OS as a native app, can take advantage of native features. They can be discovered via search engines, installed on devices with a single tap or click, and shared via a regular link. They also can work offline and get updates when new content is available.
Converting your Angular application to a PWA is easy.
## 1. Install Angular PWA
Run the following command in the root folder of your Angular application:
```shell
yarn ng add @angular/pwa
```
...or...
```shell
npm run ng add @angular/pwa
```
This will install the `@angular/service-worker` package and make your default app a PWA. Alternatively, you may add `project` parameter to target a specific app in your workspace:
```shell
yarn ng add @angular/pwa --project MyProjectName
```
Here is the output of the command:
<img alt="Angular PWA updates and creates files" src="./images/pwa-configuration-ng-add.png" width="400px" style="max-width:100%">
So, Angular CLI updates some files and add a few others:
- **ngsw-config.json** is where the [service worker configuration](https://angular.io/guide/service-worker-config) is placed. Not all PWAs have this file. It is specific to Angular.
- **manifest.webmanifest** is a [web app manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) and provides information about your app in JSON format.
- **icons** are placeholder icons that are referred to in your web app manifest. We will replace these in a minute.
- **angular.json** has following modifications:
- `assets` include _manifest.webmanifest_.
- `serviceWorker` is `true` in production build.
- `ngswConfigPath` refers to _ngsw-config.json_.
- **package.json** has _@angular/service-worker_ as a new dependency.
- **app.module.ts** imports `ServiceWorkerModule` and registers a service worker filename.
- **index.html** has following modifications:
- A `<link>` element that refers to _manifest.webmanifest_.
- A `<meta>` tag that sets a theme color.
## 2. Update the Web App Manifest
### 2.1. Set the Name of Your App
The `name` and the `short_name` properties in the generated manifest are derived from your project name. Let's change them.
Open the _manifest.webmanifest_ file and update `name` and `short_name` props:
```json
{
/* rest of the manifest meta data */
"short_name": "My Project",
"name": "My Project: My Catch-Phrase"
}
```
The short name must be really short because it will be displayed on anywhere with limited space, like the launcher and the home screen.
### 2.2. Add a Description
The `@angular/pwa` schematic we just added does not insert a description to your manifest file, but, according to [web app manifest standards](https://www.w3.org/TR/appmanifest/#description-member), you should.
So, open the _manifest.webmanifest_ file and place the description as seen below:
```json
{
/* rest of the manifest meta data */
"description": "My short project description giving a slightly better idea about my app"
}
```
As a bonus, providing a description [along with other criteria](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-edgehtml/microsoft-store#criteria-for-automatic-submission) helps Bing web crawler to index your app and automatically submit your app to Microsoft Store in `.appx` format.
### 2.3. Set App Colors
Angular generates the manifest file with a default `theme_color` and `background_color`. Change these according to your brand identity:
Open the _manifest.webmanifest_ file and update `theme_color` and `background_color` properties:
```json
{
/* rest of the manifest meta data */
"theme_color": "#000000",
"background_color": "#ffffff"
}
```
Then open _index.html_ and change the theme color meta tag as below:
```html
<meta name="theme-color" content="#000000" />
```
### 2.4. Replace App Icons & Add Splash Screens
We need to update the icons and add some splash screens. This normally is time-consuming, but we will use the marvelous [pwa-asset-generator](https://github.com/onderceylan/pwa-asset-generator#readme) library.
First, open the _manifest.webmanifest_ file and remove all elements in the `icons` property:
```json
{
/* rest of the manifest meta data */
"icons": []
}
```
Then, run the following command in your terminal (changing the path of course):
```shell
npx pwa-asset-generator /path/to/your/logo.png ./src/assets/pwa -i ./src/index.html -m ./src/manifest.webmanifest
```
Open the _manifest.webmanifest_ file again. You will see this:
```json
{
/* rest of the manifest meta data */
"icons": [
{
"src": "../manifest-icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "../manifest-icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
```
In addition to updated icons, the library will generate splash screens. However, Apple requires all splash screens to be added in your _index.html_ and displays a blank screen at startup otherwise. So, the following tags will be inserted into the _index.html_ file:
```html
<link
rel="apple-touch-icon"
sizes="180x180"
href="assets/pwa/apple-icon-180.jpg"
/>
<link
rel="apple-touch-icon"
sizes="167x167"
href="assets/pwa/apple-icon-167.jpg"
/>
<link
rel="apple-touch-icon"
sizes="152x152"
href="assets/pwa/apple-icon-152.jpg"
/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="assets/pwa/apple-icon-120.jpg"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2048-2732.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2732-2048.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1668-2388.jpg"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2388-1668.jpg"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1536-2048.jpg"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2048-1536.jpg"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1668-2224.jpg"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2224-1668.jpg"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1620-2160.jpg"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2160-1620.jpg"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1242-2688.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2688-1242.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1125-2436.jpg"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2436-1125.jpg"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-828-1792.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1792-828.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1080-1920.jpg"
media="(device-width: 360px) and (device-height: 640px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1920-1080.jpg"
media="(device-width: 360px) and (device-height: 640px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-750-1334.jpg"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1334-750.jpg"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-640-1136.jpg"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1136-640.jpg"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
```
## 3. Configure Service Worker
### 3.1 Modify Asset Groups
Angular has defined some static files to be cached by the service worker, but they are not 100% accurate. Let's change it.
Open _ngsw-config.json_ file and replace its content with this:
```json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/common-es2015.*.js",
"/main-es2015.*.js",
"/polyfills-es2015.*.js",
"/runtime-es2015.*.js",
"/vendor-es2015.*.js"
]
}
},
{
"name": "modules",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/*-es2015.*.js",
"!/common-es2015.*.js",
"!/main-es2015.*.js",
"!/polyfills-es2015.*.js",
"!/runtime-es2015.*.js",
"!/vendor-es2015.*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}
```
In case you want to cache other static files, please refer to the [service worker configuration document](https://angular.io/guide/service-worker-config#assetgroups) on Angular.io.
### 3.2 Set Data Groups
This part is unique to your project. We recommend being very careful about which endpoints to cache. Please refer to [service worker configuration document](https://angular.io/guide/service-worker-config#datagroups) on Angular.io for details.
## What's Next?
- [Config State](./Config-State.md)

2
docs/en/UI/Angular/Service-Proxies.md

@ -140,4 +140,4 @@ export class BookComponent implements OnInit {
## What's Next? ## What's Next?
- [Config State](./Config-State.md) - [PWA Configuration](./PWA-Configuration.md)

2
docs/en/UI/Angular/Toaster-Service.md

@ -67,7 +67,7 @@ With the options above, the toast overlay looks like this:
![toast](./images/toast.png) ![toast](./images/toast.png)
### How to Remove a Toast Overlay ### How to Remove a Toast Overlay
The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast: The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast:

BIN
docs/en/UI/Angular/images/pwa-configuration-ng-add.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

206
docs/en/UI/AspNetCore/Forms-Validation.md

@ -0,0 +1,206 @@
# ASP.NET Core MVC / Razor Pages: Forms & Validation
ABP Framework provides infrastructure and conventions to make easier to create forms, localize display names for the form elements and handle server & client side validation;
* [abp-dynamic-form](Tag-Helpers/Dynamic-Forms.md) tag helper automates **creating a complete form** from a C# model class: Creates the input elements, handles localization and client side validation.
* [ABP Form tag helpers](Tag-Helpers/Form-elements.md) (`abp-input`, `abp-select`, `abp-radio`...) render **a single form element** with handling localization and client side validation.
* ABP Framework automatically **localizes the display name** of a form element without needing to add a `[DisplayName]` attribute.
* **Validation errors** are automatically localized based on the user culture.
> This document is for the **client side validation** and it doesn't cover the server side validation. Check the [validation document](../../Validation.md) for server side validation infrastructure.
## The Classic Way
In a typical Bootstrap based ASP.NET Core MVC / Razor Pages UI, you [need to write](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation#client-side-validation) such a boilerplate code to create a simple form element:
````html
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
````
You can continue to use this approach if you need or prefer it. However, ABP Form tag helpers can produce the same output with a minimal code.
## ABP Dynamic Forms
[abp-dynamic-form](Tag-Helpers/Dynamic-Forms.md) tag helper completely automates the form creation. Take this model class as an example:
```csharp
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace MyProject.Web.Pages
{
public class MovieViewModel
{
[Required]
[StringLength(256)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
[TextArea]
[StringLength(1000)]
public string Description { get; set; }
public Genre Genre { get; set; }
public float? Price { get; set; }
public bool PreOrder { get; set; }
}
}
```
It uses the data annotation attributes to define validation rules and UI styles for the properties. `Genre`, is an `enum` in this example:
````csharp
namespace MyProject.Web.Pages
{
public enum Genre
{
Classic,
Action,
Fiction,
Fantasy,
Animation
}
}
````
In order to create the form in a razor page, create a property in your `PageModel` class:
```csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MyProject.Web.Pages
{
public class CreateMovieModel : PageModel
{
[BindProperty]
public MovieViewModel Movie { get; set; }
public void OnGet()
{
Movie = new MovieViewModel();
}
public async Task OnPostAsync()
{
if (ModelState.IsValid)
{
//TODO: Save the Movie
}
}
}
}
```
Then you can render the form in the `.cshtml` file:
```html
@page
@model MyProject.Web.Pages.CreateMovieModel
<h2>Create a new Movie</h2>
<abp-dynamic-form abp-model="Movie" submit-button="true" />
```
The result is shown below:
![abp-dynamic-form-result](../../images/abp-dynamic-form-result.png)
See the *Localization & Validation* section below to localize the field display names and see how the validation works.
> See [its own document](Tag-Helpers/Dynamic-Forms.md) for all options of the `abp-dynamic-form` tag helper.
## ABP Form Tag Helpers
`abp-dynamic-form` covers most of the scenarios and allows you to control and customize the form using the attributes.
However, if you want to **render the form body yourself** (for example, you may want to fully control the **form layout**), you can directly use the [ABP Form Tag Helpers](Tag-Helpers/Form-elements.md). The same auto-generated form above can be created using the ABP Form Tag Helpers as shown below:
```html
@page
@model MyProject.Web.Pages.CreateMovieModel
<h2>Create a new Movie</h2>
<form method="post">
<abp-input asp-for="Movie.Name"/>
<abp-input asp-for="Movie.ReleaseDate"/>
<abp-input asp-for="Movie.Description"/>
<abp-select asp-for="Movie.Genre"/>
<abp-input asp-for="Movie.Price"/>
<abp-input asp-for="Movie.PreOrder"/>
<abp-button button-type="Primary" type="submit">Save</abp-button>
</form>
```
> See the [ABP Form Tag Helpers](Tag-Helpers/Form-elements.md) document for details of these tag helpers and their options.
## Validation & Localization
Both of the Dynamic Form and the Form Tag Helpers **automatically validate** the input based on the data annotation attributes and shows validation error messages on the user interface. Error messages are **automatically localized** based on the current culture.
**Example: User leaves empty a required string property**
![abp-form-input-validation-error](../../images/abp-form-input-validation-error.png)
The error message below is shown if the language is French:
![abp-form-input-validation-error](../../images/abp-form-input-validation-error-french.png)
Validation errors are already [translated](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization) a lot of languages. You can [contribute](../../Contribution/Index.md) to the translation for your own language or override the texts for your own application by following the [localization](../../Localization.md) documentation.
## Display Name Localization
ABP Framework uses the property name as the field name on the user interface. You typically want to [localize](../../Localization.md) this name based on the current culture.
ABP Framework can conventionally localize the fields on the UI when you add the localization keys to the localization JSON files.
Example: French localization for the *Name* property (add into the `fr.json` in the application):
````js
"Name": "Nom"
````
Then the UI will use the given name for French language:
![abp-form-input-validation-error](../../images/abp-form-input-validation-error-french-name.png)
### Using the `DisplayName:` Prefix
Directly using the property name as the localization key may be a problem if you need to use the property name for other purpose, which a different translation value. In this case, use the `DisplayName:` prefix for the localization key:
````js
"DisplayName:Name": "Nom"
````
ABP prefers to use the `DisplayName:Name` key over the `Name` key if it does exists.
### Using a Custom Localization Key
If you need, you can use the `[DisplayName]` attribute to specify the localization key for a specific property:
````csharp
[DisplayName("MyNameKey")]
public string Name { get; set; }
````
In this case, you can add an entry to the localization file using the key `MyNameKey`.
> If you use the `[DisplayName]` but not add a corresponding entity to the localization file, then ABP Framework shows the given key as the field name, `MyNameKey` for this case. So, it provides a way to specify a hard coded display name even if you don't need to use the localization system.
## See Also
* [Server Side Validation](../../Validation.md)

206
docs/en/UI/AspNetCore/Navigation-Menu.md

@ -1,3 +1,205 @@
# Navigation Menu # ASP.NET Core MVC / Razor Pages UI: Navigation Menu
Every application has a main menu to allow users to navigate to pages/screens of the application. Some applications may contain more than one menu in different sections of the UI.
ABP Framework is a [modular](../../Module-Development-Basics.md) application development framework. **Every module may need to add items to the menu**.
So, ABP Framework **provides a menu infrastructure** where;
* The application or the modules can add items to a menu, without knowing how the menu is rendered.
* The [theme](Theming.md) properly renders the menu.
## Adding Menu Items
In order to add menu items (or manipulate the existing items) you need to create a class implementing the `IMenuContributor` interface.
**Example: Add a *CRM* menu item with *Customers* and *Orders* sub menu items**
```csharp
using System.Threading.Tasks;
using MyProject.Localization;
using Volo.Abp.UI.Navigation;
namespace MyProject.Web.Menus
{
public class MyProjectMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name == StandardMenus.Main)
{
await ConfigureMainMenuAsync(context);
}
}
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
var l = context.GetLocalizer<MyProjectResource>();
context.Menu.AddItem(
new ApplicationMenuItem("MyProject.Crm", l["Menu:CRM"])
.AddItem(new ApplicationMenuItem(
name: "MyProject.Crm.Customers",
displayName: l["Menu:Customers"],
url: "/crm/customers")
).AddItem(new ApplicationMenuItem(
name: "MyProject.Crm.Orders",
displayName: l["Menu:Orders"],
url: "/crm/orders")
)
);
}
}
}
```
* This example adds items only to the main menu (`StandardMenus.Main`: see the *Standard Menus* section below).
* It gets a `IStringLocalizer` from `context` to [localize](../../Localization.md) the display names of the menu items.
* Adds the Customers and Orders as children of the CRM menu.
Once you create a menu contributor, you need to add it to the `AbpNavigationOptions` in the `ConfigureServices` method of your module:
````csharp
Configure<AbpNavigationOptions>(options =>
{
options.MenuContributors.Add(new MyProjectMenuContributor());
});
````
When you run the application, you will see the menu items added to the main menu:
![nav-main-menu](../../images/nav-main-menu.png)
> The menu is rendered by the current UI theme. So, the look of the main menu can be completely different based on your theme.
Here, a few notes on the menu contributors;
* ABP Framework calls the `ConfigureMenuAsync` method **whenever need to render** the menu.
* Every menu item can have **children**. So, you can add menu items with **unlimited depth** (however, your UI theme may not support unlimited depth).
* Only leaf menu items have `url`s normally. When you click to a parent menu, its sub menu is opened or closed, you don't navigate the `url` of a parent menu item.
* If a menu item has no children and has no `url` defined, then it is not rendered on the UI. This simplifies to authorize the menu items: You only authorize the child items (see the next section). If none of the children are authorized, then the parent automatically disappears.
### Menu Item Properties
There are more options of a menu item (the constructor of the `ApplicationMenuItem` class). Here, the list of all available options;
* `name` (`string`, required): The unique name of the menu item.
* `displayName` (`string`, required): Display name/text of the menu item. You can [localize](../../Localization.md) this as shown before.
* `url` (`string`): The URL of the menu item.
* `icon` (`string`): An icon name. Free [Font Awesome](https://fontawesome.com/) icon classes are supported out of the box. Example: `fa fa-book`. You can use any CSS font icon class as long as you include the necessary CSS files to your application.
* `order` (`int`): The order of the menu item. Default value is `1000`. Items are sorted by the adding order unless you specify an order value.
* `customData` (`object`): A custom object that you can associate to the menu item and use it while rendering the menu item.
* `target` (`string`): Target of the menu item. Can be `null` (default), "_blank", "_*self*", "_parent", "_*top*" or a frame name for web applications.
* `elementId` (`string`): Can be used to render the element with a specific HTML `id` attribute.
* `cssClass` (`string`): Additional string classes for the menu item.
### Authorization
As seen above, a menu contributor contributes to the menu dynamically. So, you can perform any custom logic or get menu items from any source.
One use case is the [authorization](Authorization.md). You typically want to add menu items by checking a permission.
**Example: Check if the current user has a permission**
````csharp
if (await context.IsGrantedAsync("MyPermissionName"))
{
//...add menu items
}
````
> You can use `context.AuthorizationService` to directly access to the `IAuthorizationService`.
### Resolving Dependencies
`context.ServiceProvider` can be used to resolve any service dependency.
**Example: Get a service**
````csharp
var myService = context.ServiceProvider.GetRequiredService<IMyService>();
//...use the service
````
> You don't need to care about releasing/disposing services. ABP Framework handles it.
### The Administration Menu
There is a special menu item in the menu menu that is added by the ABP Framework: The *Administration* menu. It is typically used by the pre-built admin [application modules](../../Modules/Index.md):
![nav-main-menu-administration](../../images/nav-main-menu-administration.png)
If you want to add menu items under the *Administration* menu item, you can use the `context.Menu.GetAdministration()` extension method:
````csharp
context.Menu.GetAdministration().AddItem(...)
````
### Manipulating the Existing Menu Items
ABP Framework executes the menu contributors by the [module dependency order](../../Module-Development-Basics.md). So, you can manipulate the menu items that your application or module (directly or indirectly) depends on.
**Example: Set an icon for the `Users` menu item added by the [Identity Module](../../Modules/Identity.md)**
````csharp
var userMenu = context.Menu.FindMenuItem(IdentityMenuNames.Users);
userMenu.Icon = "fa fa-users";
````
> `context.Menu` gives you ability to access to all the menu items those have been added by the previous menu contributors.
## Standard Menus
A menu is a **named** component. An application may contain more than one menus with different, unique names. There are two pre-defined standard menus:
* `Main`: The main menu of the application. Contains links to the page of the application. Defined as a constant: `Volo.Abp.UI.Navigation.StandardMenus.Main`.
* `User`: User profile menu. Defined as a constant: `Volo.Abp.UI.Navigation.StandardMenus.User`.
The `Main` menu already covered above. The `User` menu is available when a user has logged in:
![user-menu](../../images/user-menu.png)
You can add items to the `User` menu by checking the `context.Menu.Name` as shown below:
```csharp
if (context.Menu.Name == StandardMenus.User)
{
//...add items
}
```
## IMenuManager
`IMenuManager` is generally used by the UI [theme](Theming.md) to render the menu items on the UI. So, **you generally don't need to directly use** the `IMenuManager`.
**Example: Getting the `Main` menu items**
```csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.UI.Navigation;
namespace MyProject.Web.Pages
{
public class IndexModel : PageModel
{
private readonly IMenuManager _menuManager;
public IndexModel(IMenuManager menuManager)
{
_menuManager = menuManager;
}
public async Task OnGetAsync()
{
var mainMenu = await _menuManager.GetAsync(StandardMenus.Main);
foreach (var menuItem in mainMenu.Items)
{
//...
}
}
}
}
```
TODO

84
docs/en/UI/AspNetCore/Page-Alerts.md

@ -0,0 +1,84 @@
# ASP.NET Core MVC / Razor Pages: Page Alerts
It is common to show error, warning or information alerts to inform the user. An example *Service Interruption* alert is shown below:
![page-alert-example](../../images/page-alert-example.png)
## Basic Usage
If you directly or indirectly inherit from `AbpPageModel`, you can use the `Alerts` property to add alerts to be rendered after the request completes.
**Example: Show a Warning alert**
```csharp
namespace MyProject.Web.Pages
{
public class IndexModel : MyProjectPageModel //or inherit from AbpPageModel
{
public void OnGet()
{
Alerts.Warning(
text: "We will have a service interruption between 02:00 AM and 04:00 AM at October 23, 2023!",
title: "Service Interruption"
);
}
}
}
```
This usage renders an alert that was shown above. If you need to localize the messages, you can always use the standard [localization](../../Localization.md) system.
### Exceptions / Invalid Model States
It is typical to show alerts when you manually handle exceptions (with try/catch statements) or want to handle `!ModelState.IsValid` case and warn the user. For example, the Account Module shows a warning if user enters an incorrect username or password:
![page-alert-account-layout](../../images/page-alert-account-layout.png)
> Note that you generally don't need to manually handle exceptions since ABP Framework provides an automatic [exception handling](../../Exception-Handling.md) system.
### Alert Types
`Warning` is used to show a warning alert. Other common methods are `Info`, `Danger` and `Success`.
Beside the standard methods, you can use the `Alerts.Add` method by passing an `AlertType` `enum` with one of these values: `Default`, `Primary`, `Secondary`, `Success`, `Danger`, `Warning`, `Info`, `Light`, `Dark`.
### Dismissible
All alert methods gets an optional `dismissible` parameter. Default value is `true` which makes the alert box dismissible. Set it to `false` to create a sticky alert box.
## IAlertManager
If you need to add alert messages from another part of your code, you can inject the `IAlertManager` service and use its `Alerts` list.
**Example: Inject the `IAlertManager`**
```csharp
using Volo.Abp.AspNetCore.Mvc.UI.Alerts;
using Volo.Abp.DependencyInjection;
namespace MyProject.Web.Pages
{
public class MyService : ITransientDependency
{
private readonly IAlertManager _alertManager;
public MyService(IAlertManager alertManager)
{
_alertManager = alertManager;
}
public void Test()
{
_alertManager.Alerts.Add(AlertType.Danger, "Test message!");
}
}
}
```
## Notes
### AJAX Requests
Page Alert system was designed to be used in a regular full page request. It is not for AJAX/partial requests. The alerts are rendered in the page layout, so a full page refresh is needed.
For AJAX requests, it is more proper to throw exceptions (e.g. `UserFriendlyException`). See the [exception handling](../../Exception-Handling.md) document.

8
docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md

@ -1,7 +1,5 @@
# Dynamic Forms # Dynamic Forms
`Warning:` Before getting into this document, be sure that you have clearly understood [abp form elements](Form-elements.md) document.
## Introduction ## Introduction
`abp-dynamic-form` creates a bootstrap form for a given c# model. `abp-dynamic-form` creates a bootstrap form for a given c# model.
@ -272,4 +270,8 @@ You can set it yourself by using `[Display()]` attribute of Asp.Net Core. You ca
````csharp ````csharp
[Display(Name = "Name")] [Display(Name = "Name")]
public string Name { get; set; } public string Name { get; set; }
```` ````
## See Also
* [Form Elements](Form-elements.md)

6
docs/en/UI/Blazor/Overall.md

@ -0,0 +1,6 @@
# Blazor UI for the ABP Framework
The detailed documentation for the Blazor UI is in progress. However, you can follow the documents below to start with the Blazor UI today.
* [Get started](https://docs.abp.io/en/abp/latest/Getting-Started?UI=Blazor) with the Blazor UI for the ABP Framework.
* [Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=Blazor) with the Blazor UI.

3
docs/en/UI/Blazor/Services/Notification.md

@ -0,0 +1,3 @@
# Blazor UI Notification
`UiNotificationService` is used to show toastr style notifications on the user interface. The documentation is in progress...

44
docs/en/docs-nav.json

@ -156,7 +156,13 @@
}, },
{ {
"text": "Caching", "text": "Caching",
"path": "Caching.md" "path": "Caching.md",
"items": [
{
"text": "Redis Cache",
"path": "Redis-Cache.md"
}
]
}, },
{ {
"text": "Logging", "text": "Logging",
@ -386,8 +392,20 @@
"text": "User Interface", "text": "User Interface",
"items": [ "items": [
{ {
"text": "ASP.NET Core", "text": "ASP.NET Core MVC / Razor Pages",
"items": [ "items": [
{
"text": "Forms & Validation",
"path": "UI/AspNetCore/Forms-Validation.md"
},
{
"text": "Navigation / Menus",
"path": "UI/AspNetCore/Navigation-Menu.md"
},
{
"text": "Page Alerts",
"path": "UI/AspNetCore/Page-Alerts.md"
},
{ {
"text": "Client Side Package Management", "text": "Client Side Package Management",
"path": "UI/AspNetCore/Client-Side-Package-Management.md" "path": "UI/AspNetCore/Client-Side-Package-Management.md"
@ -424,6 +442,24 @@
} }
] ]
}, },
{
"text": "Blazor",
"items": [
{
"text": "Overall",
"path": "UI/Blazor/Overall.md"
},
{
"text": "Services",
"items": [
{
"text": "Overall",
"path": "UI/Blazor/Services/Notification.md"
}
]
}
]
},
{ {
"text": "Angular", "text": "Angular",
"items": [ "items": [
@ -449,6 +485,10 @@
{ {
"text": "Service Proxies", "text": "Service Proxies",
"path": "UI/Angular/Service-Proxies.md" "path": "UI/Angular/Service-Proxies.md"
},
{
"text": "PWA Configuration",
"path": "UI/Angular/PWA-Configuration.md"
} }
] ]
}, },

BIN
docs/en/images/abp-dynamic-form-result.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/images/abp-form-input-validation-error-french-name.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
docs/en/images/abp-form-input-validation-error-french.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
docs/en/images/abp-form-input-validation-error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
docs/en/images/nav-main-menu-administration.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/images/nav-main-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/images/page-alert-account-layout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/en/images/page-alert-example.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/images/user-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

131
framework/.editorconfig

@ -0,0 +1,131 @@
# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\Volosoft\abp\framework codebase based on best match to current usage at 2.10.2020.
# You can modify the rules from these initially generated values to suit your own policies
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
[*.cs]
#Core editorconfig formatting - indentation
#use soft tabs (spaces) for indentation
indent_style = space
#Formatting - indentation options
#indent switch case contents.
csharp_indent_case_contents = true
#indent switch labels
csharp_indent_switch_labels = true
#Formatting - new line options
#place catch statements on a new line
csharp_new_line_before_catch = true
#place else statements on a new line
csharp_new_line_before_else = true
#require members of object intializers to be on separate lines
csharp_new_line_before_members_in_object_initializers = true
#require braces to be on a new line for accessors, methods, lambdas, object_collection_array_initializers, control_blocks, types, and properties (also known as "Allman" style)
csharp_new_line_before_open_brace = accessors, methods, lambdas, object_collection_array_initializers, control_blocks, types, properties
#Formatting - organize using options
#sort System.* using directives alphabetically, and place them before other usings
dotnet_sort_system_directives_first = true
#Formatting - spacing options
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
#Formatting - wrapping options
#leave code block on single line
csharp_preserve_single_line_blocks = true
#Style - Code block preferences
#prefer curly braces even for one line of code
csharp_prefer_braces = true:suggestion
#Style - expression bodied member options
#prefer block bodies for constructors
csharp_style_expression_bodied_constructors = false:suggestion
#prefer block bodies for methods
csharp_style_expression_bodied_methods = false:suggestion
#prefer expression-bodied members for properties
csharp_style_expression_bodied_properties = true:suggestion
#Style - expression level options
#prefer out variables to be declared inline in the argument list of a method call when possible
csharp_style_inlined_variable_declaration = true:suggestion
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true:suggestion
#Style - Expression-level preferences
#prefer default over default(T)
csharp_prefer_simple_default_expression = true:suggestion
#prefer objects to be initialized using object initializers when possible
dotnet_style_object_initializer = true:suggestion
#prefer inferred tuple element names
dotnet_style_prefer_inferred_tuple_names = true:suggestion
#Style - implicit and explicit types
#prefer var over explicit type in all cases, unless overridden by another code style rule
csharp_style_var_elsewhere = true:suggestion
#prefer var is used to declare variables with built-in system types such as int
csharp_style_var_for_built_in_types = true:suggestion
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
csharp_style_var_when_type_is_apparent = true:suggestion
#Style - language keyword and framework type options
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#Style - modifier options
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
#Style - Modifier preferences
#when this rule is set to a list of modifiers, prefer the specified ordering.
csharp_preferred_modifier_order = public,protected,private,virtual,async,static,override,readonly,abstract:suggestion
#Style - Pattern matching
#prefer pattern matching instead of is expression with type casts
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
#Style - qualification options
#prefer fields not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_field = false:suggestion
#prefer methods not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_method = false:suggestion
#prefer properties not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_property = false:suggestion

6
framework/Volo.Abp.sln

@ -348,6 +348,7 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Authentication.OpenIdConnect", "src\Volo.Abp.AspNetCore.Authentication.OpenIdConnect\Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj", "{DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Authentication.OpenIdConnect", "src\Volo.Abp.AspNetCore.Authentication.OpenIdConnect\Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj", "{DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EventBus.Rebus", "src\Volo.Abp.EventBus.Rebus\Volo.Abp.EventBus.Rebus.csproj", "{F689967F-1EF1-4D75-8BA4-2F2F3506B1F3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EventBus.Rebus", "src\Volo.Abp.EventBus.Rebus\Volo.Abp.EventBus.Rebus.csproj", "{F689967F-1EF1-4D75-8BA4-2F2F3506B1F3}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ExceptionHandling", "src\Volo.Abp.ExceptionHandling\Volo.Abp.ExceptionHandling.csproj", "{B9D1ADCB-D552-4626-A1F1-78FF72C1E822}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -1039,6 +1040,10 @@ Global
{F689967F-1EF1-4D75-8BA4-2F2F3506B1F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {F689967F-1EF1-4D75-8BA4-2F2F3506B1F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F689967F-1EF1-4D75-8BA4-2F2F3506B1F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {F689967F-1EF1-4D75-8BA4-2F2F3506B1F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F689967F-1EF1-4D75-8BA4-2F2F3506B1F3}.Release|Any CPU.Build.0 = Release|Any CPU {F689967F-1EF1-4D75-8BA4-2F2F3506B1F3}.Release|Any CPU.Build.0 = Release|Any CPU
{B9D1ADCB-D552-4626-A1F1-78FF72C1E822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9D1ADCB-D552-4626-A1F1-78FF72C1E822}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9D1ADCB-D552-4626-A1F1-78FF72C1E822}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9D1ADCB-D552-4626-A1F1-78FF72C1E822}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1215,6 +1220,7 @@ Global
{37F89B0B-1C6B-426F-A5EE-676D1956D9E9} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {37F89B0B-1C6B-426F-A5EE-676D1956D9E9} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{F689967F-1EF1-4D75-8BA4-2F2F3506B1F3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {F689967F-1EF1-4D75-8BA4-2F2F3506B1F3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{B9D1ADCB-D552-4626-A1F1-78FF72C1E822} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

6
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicTheme.cs

@ -1,5 +1,6 @@
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; using Volo.Abp.AspNetCore.Components.WebAssembly.Theming;
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing; using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing;
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme
@ -15,6 +16,11 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme
{ {
options.AdditionalAssemblies.Add(typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule).Assembly); options.AdditionalAssemblies.Add(typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule).Assembly);
}); });
Configure<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new BasicThemeToolbarContributor());
});
} }
} }
} }

20
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BasicThemeToolbarContributor.cs

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic;
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme
{
public class BasicThemeToolbarContributor : IToolbarContributor
{
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if (context.Toolbar.Name == StandardToolbars.Main)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(LanguageSwitch)));
context.Toolbar.Items.Add(new ToolbarItem(typeof(LoginDisplay)));
}
return Task.CompletedTask;
}
}
}

7
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor

@ -17,7 +17,10 @@
<DropdownItem Clicked="@(() => NavigateTo(menuItem.Url))">@menuItem.DisplayName</DropdownItem> <DropdownItem Clicked="@(() => NavigateTo(menuItem.Url))">@menuItem.DisplayName</DropdownItem>
} }
} }
<Divider /> <a class="dropdown-item" href="@ServerAccountUrl">
@UiLocalizer["ManageYourAccount"]
</a>
<DropdownDivider />
<DropdownItem Clicked="BeginSignOut">Logout</DropdownItem> <DropdownItem Clicked="BeginSignOut">Logout</DropdownItem>
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>
@ -32,7 +35,7 @@
{ {
Navigation.NavigateTo(uri); Navigation.NavigateTo(uri);
} }
private async Task BeginSignOut() private async Task BeginSignOut()
{ {
await SignOutManager.SetSignOutState(); await SignOutManager.SetSignOutState();

40
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs

@ -1,18 +1,50 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.Http.Client;
using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic
{ {
public partial class LoginDisplay public partial class LoginDisplay : IDisposable
{ {
[Inject] protected IMenuManager MenuManager { get; set; } [Inject]
protected IMenuManager MenuManager { get; set; }
[Inject]
protected IStringLocalizer<AbpUiResource> UiLocalizer { get; set; }
[Inject]
protected IOptions<AbpRemoteServiceOptions> RemoteServiceOptions { get; set; }
protected ApplicationMenu Menu { get; set; } protected ApplicationMenu Menu { get; set; }
protected string ServerUrl { get; set; }
protected string ServerAccountUrl { get; set; }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
Menu = await MenuManager.GetAsync(StandardMenus.User); Menu = await MenuManager.GetAsync(StandardMenus.User);
ServerUrl = RemoteServiceOptions.Value.RemoteServices.Default?.BaseUrl?.TrimEnd('/');
ServerAccountUrl = ServerUrl + "/Account/Manage?returnUrl=" + Navigation.Uri;
Navigation.LocationChanged += OnLocationChanged;
}
protected virtual void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
ServerAccountUrl = ServerUrl + "/Account/Manage?returnUrl=" + Navigation.Uri;
StateHasChanged();
}
public void Dispose()
{
Navigation.LocationChanged -= OnLocationChanged;
} }
} }
} }

9
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor

@ -13,14 +13,7 @@
<ul class="navbar-nav mx-auto"> <ul class="navbar-nav mx-auto">
<NavMenu/> <NavMenu/>
</ul> </ul>
<ul class="navbar-nav"> <NavToolbar/>
<li class="nav-item">
<LanguageSwitch/>
</li>
<li class="nav-item">
<LoginDisplay/>
</li>
</ul>
</div> </div>
</div> </div>
</nav> </nav>

46
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor

@ -2,6 +2,50 @@
{ {
foreach (var menuItem in Menu.Items) foreach (var menuItem in Menu.Items)
{ {
<NavMenuItem MenuItem="@menuItem" /> var elementId = menuItem.ElementId ?? "MenuItem_" + menuItem.Name.Replace(".", "_");
var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass;
var disabled = menuItem.IsDisabled ? "disabled" : string.Empty;
var url = string.IsNullOrEmpty(menuItem.Url) ? "#" : menuItem.Url;
if (menuItem.IsLeaf)
{
if (menuItem.Url != null)
{
<li class="nav-item @cssClass @disabled" id="@elementId">
<a class="nav-link" href="@url">
@if (menuItem.Icon != null)
{
if (menuItem.Icon.StartsWith("fa"))
{
<i class="@menuItem.Icon"></i>
}
}
@menuItem.DisplayName
</a>
</li>
}
}
else
{
<li class="nav-item">
<div class="dropdown">
<a class="nav-link dropdown-toggle" href="#" id="Menu_@(menuItem.Name)" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@if (menuItem.Icon != null)
{
if (menuItem.Icon.StartsWith("fa"))
{
<i class="@menuItem.Icon"></i>
}
}
@menuItem.DisplayName
</a>
<div class="dropdown-menu border-0 shadow-sm" aria-labelledby="Menu_@(menuItem.Name)">
@foreach (var childMenuItem in menuItem.Items)
{
<NavMenuItem MenuItem="childMenuItem"/>
}
</div>
</div>
</li>
}
} }
} }

55
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenuItem.razor

@ -2,51 +2,46 @@
@{ @{
var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_"); var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_");
var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass; var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass;
var disabled = MenuItem.IsDisabled ? "disabled" : string.Empty;
var url = string.IsNullOrEmpty(MenuItem.Url) ? "#" : MenuItem.Url; var url = string.IsNullOrEmpty(MenuItem.Url) ? "#" : MenuItem.Url;
} }
@if (MenuItem.IsLeaf) @if (MenuItem.IsLeaf)
{ {
if (MenuItem.Url != null) if (MenuItem.Url != null)
{ {
<li class="nav-item @cssClass" disabled="@MenuItem.IsDisabled"> <a class="dropdown-item @cssClass @disabled" href="@url" id="@elementId">
<NavLink class="nav-link" href="@url" id="@elementId"> @if (MenuItem.Icon != null)
@if (MenuItem.Icon != null) {
if (MenuItem.Icon.StartsWith("fa"))
{ {
if (MenuItem.Icon.StartsWith("fa")) <i class="@MenuItem.Icon"></i>
{
<i class="@MenuItem.Icon"></i>
}
} }
@MenuItem.DisplayName }
</NavLink> @MenuItem.DisplayName
</li> </a>
} }
} }
else else
{ {
<li class="nav-item"> <div class="dropdown-submenu">
<div class="dropdown"> <a role="button" class="btn dropdown-toggle" data-toggle="dropdown"
<a class="nav-link dropdown-toggle" href="#" id="@elementId" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> aria-haspopup="true" aria-expanded="false">
@if (MenuItem.Icon != null) <span class="lp-icon">
{ <i class="@(MenuItem.Icon ?? "")"></i>
if (MenuItem.Icon.StartsWith("fa")) </span>
{ <span class="lp-text">
<i class="@MenuItem.Icon"></i>
}
}
@MenuItem.DisplayName @MenuItem.DisplayName
</a> </span>
<div class="dropdown-menu border-0 shadow-sm" aria-labelledby="@elementId"> </a>
@foreach (var childMenuItem in MenuItem.Items) <div class="dropdown-menu border-0 shadow-sm">
{ @foreach (var childMenuItem in MenuItem.Items)
<NavMenuItem MenuItem="@childMenuItem" /> {
} <NavMenuItem MenuItem="@childMenuItem"/>
</div> }
</div> </div>
</li> </div>
} }
@code { @code {
[Parameter] [Parameter]
public ApplicationMenuItem MenuItem { get; set; } public ApplicationMenuItem MenuItem { get; set; }
} }

8
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavToolbar.razor

@ -0,0 +1,8 @@
<ul class="navbar-nav">
@foreach (var render in ToolbarItemRenders)
{
<li class="nav-item">
@render
</li>
}
</ul>

32
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavToolbar.razor.cs

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic
{
public partial class NavToolbar
{
[Inject]
private IToolbarManager ToolbarManager { get; set; }
private List<RenderFragment> ToolbarItemRenders { get; set; } = new List<RenderFragment>();
protected override async Task OnInitializedAsync()
{
var toolbar = await ToolbarManager.GetAsync(StandardToolbars.Main);
ToolbarItemRenders.Clear();
foreach (var item in toolbar.Items)
{
ToolbarItemRenders.Add(builder =>
{
builder.OpenComponent(0, item.ComponentType);
builder.CloseComponent();
});
}
}
}
}

31
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Globalization;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -6,6 +7,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.AspNetCore.Components.WebAssembly; using Volo.Abp.AspNetCore.Components.WebAssembly;
using Volo.Abp.AspNetCore.Mvc.Client;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
@ -30,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
return application; return application;
} }
public static async Task InitializeAsync( public async static Task InitializeAsync(
[NotNull] this IAbpApplicationWithExternalServiceProvider application, [NotNull] this IAbpApplicationWithExternalServiceProvider application,
[NotNull] IServiceProvider serviceProvider) [NotNull] IServiceProvider serviceProvider)
{ {
@ -41,10 +43,29 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
using (var scope = serviceProvider.CreateScope()) using (var scope = serviceProvider.CreateScope())
{ {
foreach (var service in scope.ServiceProvider.GetServices<IAsyncInitialize>()) await InitializeModulesAsync(scope.ServiceProvider);
{ SetCurrentLanguage(scope);
await service.InitializeAsync(); }
} }
private async static Task InitializeModulesAsync(IServiceProvider serviceProvider)
{
foreach (var service in serviceProvider.GetServices<IAsyncInitialize>())
{
await service.InitializeAsync();
}
}
private static void SetCurrentLanguage(IServiceScope scope)
{
var configurationClient = scope.ServiceProvider.GetRequiredService<ICachedApplicationConfigurationClient>();
var configuration = configurationClient.Get();
var cultureName = configuration.Localization?.CurrentCulture?.CultureName;
if (!cultureName.IsNullOrEmpty())
{
var culture = new CultureInfo(cultureName);
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
} }
} }
} }

17
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/Extensions/DependencyInjection/AbpWebAssemblyServiceCollectionExtensions.cs

@ -0,0 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Volo.Abp;
namespace Microsoft.Extensions.DependencyInjection
{
public static class AbpWebAssemblyServiceCollectionExtensions
{
public static WebAssemblyHostBuilder GetHostBuilder(
[NotNull] this IServiceCollection services)
{
Check.NotNull(services, nameof(services));
return services.GetSingletonInstance<WebAssemblyHostBuilder>();
}
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs

@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Client; using Volo.Abp.AspNetCore.Mvc.Client;
using Volo.Abp.Http.Client; using Volo.Abp.Http.Client;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -22,5 +24,12 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly
}); });
}); });
} }
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services
.GetHostBuilder().Logging
.AddProvider(new AbpExceptionHandlingLoggerProvider(context.Services));
}
} }
} }

72
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/AbpExceptionHandlingLogger.cs

@ -0,0 +1,72 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling
{
public class AbpExceptionHandlingLogger : ILogger, IDisposable
{
private readonly IServiceCollection _serviceCollection;
private IServiceScope _serviceScope;
private IUserExceptionInformer _userExceptionInformer;
public AbpExceptionHandlingLogger(IServiceCollection serviceCollection)
{
_serviceCollection = serviceCollection;
}
public virtual void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
if (exception == null)
{
return;
}
if (logLevel != LogLevel.Critical && logLevel != LogLevel.Error)
{
return;
}
TryInitialize();
if (_userExceptionInformer == null)
{
return;
}
_userExceptionInformer.InformAsync(new UserExceptionInformerContext(exception));
}
protected virtual void TryInitialize()
{
var serviceProvider = _serviceCollection.GetServiceProviderOrNull();
if (serviceProvider == null)
{
return;
}
_serviceScope = serviceProvider.CreateScope();
_userExceptionInformer = _serviceScope.ServiceProvider.GetRequiredService<IUserExceptionInformer>();
}
public virtual bool IsEnabled(LogLevel logLevel)
{
return logLevel == LogLevel.Critical || logLevel == LogLevel.Error;
}
public virtual IDisposable BeginScope<TState>(TState state)
{
return NullDisposable.Instance;
}
public virtual void Dispose()
{
_serviceScope?.Dispose();
}
}
}

38
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/AbpExceptionHandlingLoggerProvider.cs

@ -0,0 +1,38 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling
{
public class AbpExceptionHandlingLoggerProvider : ILoggerProvider
{
private AbpExceptionHandlingLogger _logger;
private static readonly object SyncObj = new object();
private readonly IServiceCollection _serviceCollection;
public AbpExceptionHandlingLoggerProvider(IServiceCollection serviceCollection)
{
_serviceCollection = serviceCollection;
}
public ILogger CreateLogger(string categoryName)
{
if (_logger == null)
{
lock (SyncObj)
{
if (_logger == null)
{
_logger = new AbpExceptionHandlingLogger(_serviceCollection);
}
}
}
return _logger;
}
public void Dispose()
{
_logger.Dispose();
}
}
}

10
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/IUserExceptionInformer.cs

@ -0,0 +1,10 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling
{
public interface IUserExceptionInformer
{
Task InformAsync(UserExceptionInformerContext context);
}
}

49
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs

@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
using Volo.Abp.Http.Client;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling
{
public class UserExceptionInformer : IUserExceptionInformer, ITransientDependency
{
protected IUiMessageService MessageService { get; }
protected IExceptionToErrorInfoConverter ExceptionToErrorInfoConverter { get; }
public UserExceptionInformer(IUiMessageService messageService, IExceptionToErrorInfoConverter exceptionToErrorInfoConverter)
{
MessageService = messageService;
ExceptionToErrorInfoConverter = exceptionToErrorInfoConverter;
}
public virtual async Task InformAsync(UserExceptionInformerContext context)
{
var errorInfo = GetErrorInfo(context);
await ShowErrorInfoAsync(errorInfo);
}
protected virtual RemoteServiceErrorInfo GetErrorInfo(UserExceptionInformerContext context)
{
if (context.Exception is AbpRemoteCallException remoteCallException)
{
return remoteCallException.Error;
}
return ExceptionToErrorInfoConverter.Convert(context.Exception, false);
}
protected virtual async Task ShowErrorInfoAsync(RemoteServiceErrorInfo errorInfo)
{
if (errorInfo.Details.IsNullOrEmpty())
{
await MessageService.ErrorAsync(errorInfo.Message);
}
else
{
await MessageService.ErrorAsync(errorInfo.Details, errorInfo.Message);
}
}
}
}

16
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformerContext.cs

@ -0,0 +1,16 @@
using System;
using JetBrains.Annotations;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling
{
public class UserExceptionInformerContext
{
[NotNull]
public Exception Exception { get; }
public UserExceptionInformerContext(Exception exception)
{
Exception = exception;
}
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
public interface IUiNotificationService
{
Task Info(string message);
}
}

13
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs

@ -0,0 +1,13 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
public class NullUiNotificationService : IUiNotificationService, ITransientDependency
{
public Task Info(string message)
{
return Task.CompletedTask;
}
}
}

48
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs

@ -1,5 +1,4 @@
using System; using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -8,59 +7,24 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
{ {
public class MultiTenancyMiddleware : IMiddleware, ITransientDependency public class MultiTenancyMiddleware : IMiddleware, ITransientDependency
{ {
private readonly ITenantResolver _tenantResolver; private readonly ITenantConfigurationProvider _tenantConfigurationProvider;
private readonly ITenantStore _tenantStore;
private readonly ICurrentTenant _currentTenant; private readonly ICurrentTenant _currentTenant;
private readonly ITenantResolveResultAccessor _tenantResolveResultAccessor;
public MultiTenancyMiddleware( public MultiTenancyMiddleware(
ITenantResolver tenantResolver, ITenantConfigurationProvider tenantConfigurationProvider,
ITenantStore tenantStore, ICurrentTenant currentTenant)
ICurrentTenant currentTenant,
ITenantResolveResultAccessor tenantResolveResultAccessor)
{ {
_tenantResolver = tenantResolver; _tenantConfigurationProvider = tenantConfigurationProvider;
_tenantStore = tenantStore;
_currentTenant = currentTenant; _currentTenant = currentTenant;
_tenantResolveResultAccessor = tenantResolveResultAccessor;
} }
public async Task InvokeAsync(HttpContext context, RequestDelegate next) public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{ {
var resolveResult = _tenantResolver.ResolveTenantIdOrName(); var tenant = await _tenantConfigurationProvider.GetAsync(saveResolveResult: true);
_tenantResolveResultAccessor.Result = resolveResult;
TenantConfiguration tenant = null;
if (resolveResult.TenantIdOrName != null)
{
tenant = await FindTenantAsync(resolveResult.TenantIdOrName);
if (tenant == null)
{
throw new BusinessException(
code: "Volo.AbpIo.MultiTenancy:010001",
message: "Tenant not found!",
details: "There is no tenant with the tenant id or name: " + resolveResult.TenantIdOrName
);
}
}
using (_currentTenant.Change(tenant?.Id, tenant?.Name)) using (_currentTenant.Change(tenant?.Id, tenant?.Name))
{ {
await next(context); await next(context);
} }
} }
private async Task<TenantConfiguration> FindTenantAsync(string tenantIdOrName)
{
if (Guid.TryParse(tenantIdOrName, out var parsedTenantId))
{
return await _tenantStore.FindAsync(parsedTenantId);
}
else
{
return await _tenantStore.FindAsync(tenantIdOrName);
}
}
} }
} }

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

@ -1,14 +1,5 @@
using System; using System.Threading.Tasks;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
using Volo.Abp.Threading;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers
{ {
@ -52,4 +43,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
} }

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

@ -1,6 +1,7 @@
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Collections.Generic;
using System.Text.Encodings.Web;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
@ -14,6 +15,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb
{ {
_encoder = encoder; _encoder = encoder;
} }
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
output.TagName = "li"; output.TagName = "li";
@ -24,7 +26,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb
var list = context.GetValue<List<BreadcrumbItem>>(BreadcrumbItemsContent); var list = context.GetValue<List<BreadcrumbItem>>(BreadcrumbItemsContent);
output.Content.SetHtmlContent(GetInnerHtml(context, output)); output.Content.SetHtmlContent(GetInnerHtml(context, output));
list.Add(new BreadcrumbItem list.Add(new BreadcrumbItem
{ {
Html = output.Render(_encoder), Html = output.Render(_encoder),
@ -39,10 +41,13 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb
if (string.IsNullOrWhiteSpace(TagHelper.Href)) if (string.IsNullOrWhiteSpace(TagHelper.Href))
{ {
output.Attributes.Add("aria-current", "page"); output.Attributes.Add("aria-current", "page");
return TagHelper.Title; return _encoder.Encode(TagHelper.Title);
} }
return "<a href=\"" + TagHelper.Href + "\">" + TagHelper.Title + "</a>";
}
var link = new TagBuilder("a");
link.Attributes.Add("href", TagHelper.Href);
link.InnerHtml.Append(TagHelper.Title);
return link.ToHtmlString();
}
} }
} }

32
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs

@ -1,8 +1,8 @@
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb
{ {
@ -10,37 +10,37 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb
{ {
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
output.TagName = "nav";
output.Attributes.Add("aria-label", "breadcrumb");
var list = InitilizeFormGroupContentsContext(context, output); var list = InitilizeFormGroupContentsContext(context, output);
await output.GetChildContentAsync(); await output.GetChildContentAsync();
SetInnerOlTag(context, output); var listTagBuilder = GetOlTagBuilder();
SetInnerList(context, output, list);
output.TagName = "nav"; SetInnerList(context, output, list, listTagBuilder);
output.Attributes.Add("aria-label", "breadcrumb");
output.Content.SetHtmlContent(listTagBuilder);
} }
protected virtual void SetInnerOlTag(TagHelperContext context, TagHelperOutput output) protected virtual TagBuilder GetOlTagBuilder()
{ {
output.PreContent.SetHtmlContent("<ol class=\"breadcrumb\">"); var builder = new TagBuilder("ol");
output.PostContent.SetHtmlContent("</ol>"); builder.AddCssClass("breadcrumb");
return builder;
} }
protected virtual void SetInnerList(TagHelperContext context, TagHelperOutput output, List<BreadcrumbItem> list) protected virtual void SetInnerList(TagHelperContext context, TagHelperOutput output, List<BreadcrumbItem> list, TagBuilder listTagBuilder)
{ {
SetLastOneActiveIfThereIsNotAny(context, output, list); SetLastOneActiveIfThereIsNotAny(context, output, list);
var html = new StringBuilder("");
foreach (var breadcrumbItem in list) foreach (var breadcrumbItem in list)
{ {
var htmlPart = SetActiveClassIfActiveAndGetHtml(breadcrumbItem); var htmlPart = SetActiveClassIfActiveAndGetHtml(breadcrumbItem);
html.AppendLine(htmlPart); listTagBuilder.InnerHtml.AppendHtml(htmlPart);
} }
output.Content.SetHtmlContent(html.ToString());
} }
protected virtual List<BreadcrumbItem> InitilizeFormGroupContentsContext(TagHelperContext context, TagHelperOutput output) protected virtual List<BreadcrumbItem> InitilizeFormGroupContentsContext(TagHelperContext context, TagHelperOutput output)

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonTagHelper.cs

@ -19,6 +19,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
public FontIconType IconType { get; set; } = FontIconType.FontAwesome; public FontIconType IconType { get; set; } = FontIconType.FontAwesome;
public bool BusyTextIsHtml { get; set; }
public AbpButtonTagHelper(AbpButtonTagHelperService service) public AbpButtonTagHelper(AbpButtonTagHelperService service)
: base(service) : base(service)
{ {

16
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonTagHelperService.cs

@ -1,13 +1,14 @@
using System; using Localization.Resources.AbpUi;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using System;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
{ {
public class AbpButtonTagHelperService : AbpButtonTagHelperServiceBase<AbpButtonTagHelper> public class AbpButtonTagHelperService : AbpButtonTagHelperServiceBase<AbpButtonTagHelper>
{ {
protected const string DataBusyTextAttributeName = "data-busy-text"; protected const string DataBusyTextAttributeName = "data-busy-text";
protected const string DataBusyTextIsHtmlAttributeName = "data-busy-text-is-html";
protected IStringLocalizer<AbpUiResource> L { get; } protected IStringLocalizer<AbpUiResource> L { get; }
@ -22,6 +23,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
output.TagName = "button"; output.TagName = "button";
AddType(context, output); AddType(context, output);
AddBusyText(context, output); AddBusyText(context, output);
AddBusyTextIsHtml(context, output);
} }
protected virtual void AddType(TagHelperContext context, TagHelperOutput output) protected virtual void AddType(TagHelperContext context, TagHelperOutput output)
@ -44,5 +46,15 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
output.Attributes.SetAttribute(DataBusyTextAttributeName, busyText); output.Attributes.SetAttribute(DataBusyTextAttributeName, busyText);
} }
protected virtual void AddBusyTextIsHtml(TagHelperContext context, TagHelperOutput output)
{
if (!TagHelper.BusyTextIsHtml)
{
return;
}
output.Attributes.SetAttribute(DataBusyTextIsHtmlAttributeName, TagHelper.BusyTextIsHtml.ToString().ToLower());
}
} }
} }

11
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonTagHelperServiceBase.cs

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System; using System;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
@ -43,7 +44,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
return; return;
} }
output.Content.AppendHtml($"<i class=\"{GetIconClass(context, output)}\"></i> "); var icon = new TagBuilder("i");
icon.AddCssClass(GetIconClass(context, output));
output.Content.AppendHtml(icon);
} }
protected virtual string GetIconClass(TagHelperContext context, TagHelperOutput output) protected virtual string GetIconClass(TagHelperContext context, TagHelperOutput output)
@ -64,7 +67,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
return; return;
} }
output.Content.AppendHtml($"<span>{TagHelper.Text}</span>"); var span = new TagBuilder("span");
span.InnerHtml.Append(TagHelper.Text);
output.Content.AppendHtml(span);
} }
protected virtual void AddDisabled(TagHelperContext context, TagHelperOutput output) protected virtual void AddDisabled(TagHelperContext context, TagHelperOutput output)

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

@ -1,7 +1,7 @@
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Collections.Generic;
using System.Text.Encodings.Web;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
@ -42,8 +42,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
protected virtual void SetInnerImgTag(TagHelperContext context, TagHelperOutput output) protected virtual void SetInnerImgTag(TagHelperContext context, TagHelperOutput output)
{ {
var imgTag ="<img class=\"d-block w-100\" src=\""+TagHelper.Src+ "\" alt=\"" + TagHelper.Alt + "\">"; var img = new TagBuilder("img");
output.Content.SetHtmlContent(imgTag); img.AddCssClass("d-block w-100");
img.Attributes.Add("src", TagHelper.Src);
img.Attributes.Add("alt", TagHelper.Alt);
output.Content.SetHtmlContent(img);
} }
protected virtual void SetActive(TagHelperContext context, TagHelperOutput output) protected virtual void SetActive(TagHelperContext context, TagHelperOutput output)
@ -61,15 +65,19 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
return; return;
} }
var html = new StringBuilder(""); var title = new TagBuilder("h5");
title.InnerHtml.Append(TagHelper.CaptionTitle);
var caption = new TagBuilder("p");
caption.InnerHtml.Append(TagHelper.Caption);
html.AppendLine("<div class=\"carousel-caption d-none d-md-block\">"); var wrapper = new TagBuilder("div");
html.AppendLine("<h5>"+TagHelper.CaptionTitle+"</h5>"); wrapper.AddCssClass("carousel-caption d-none d-md-block");
html.AppendLine("<p>" + TagHelper.Caption + "</p>"); wrapper.InnerHtml.AppendHtml(title);
html.AppendLine("</div>"); wrapper.InnerHtml.AppendHtml(caption);
output.PostContent.SetHtmlContent(html.ToString()); output.PostContent.SetHtmlContent(wrapper);
} }
} }
} }

97
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Carousel/AbpCarouselTagHelperService.cs

@ -1,15 +1,24 @@
using System; using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
{ {
public class AbpCarouselTagHelperService : AbpTagHelperService<AbpCarouselTagHelper> public class AbpCarouselTagHelperService : AbpTagHelperService<AbpCarouselTagHelper>
{ {
protected IStringLocalizer<AbpUiResource> L { get; }
public AbpCarouselTagHelperService(IStringLocalizer<AbpUiResource> localizer)
{
L = localizer;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
output.TagName = "div"; output.TagName = "div";
@ -37,18 +46,17 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
protected virtual void SetItems(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList) protected virtual void SetItems(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList)
{ {
var itemsHtml = new StringBuilder(""); var wrapper = new TagBuilder("div");
itemsHtml.Append("<div class= \"carousel-inner\">"); wrapper.AddCssClass("carousel-inner");
foreach (var carouselItem in itemList) foreach (var carouselItem in itemList)
{ {
SetActiveIfActive(carouselItem); SetActiveIfActive(carouselItem);
itemsHtml.AppendLine(carouselItem.Html); wrapper.InnerHtml.AppendHtml(carouselItem.Html);
} }
itemsHtml.Append("</div>"); output.Content.SetHtmlContent(wrapper);
output.Content.SetHtmlContent(itemsHtml.ToString());
} }
protected virtual void SetControls(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList) protected virtual void SetControls(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList)
@ -58,18 +66,45 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
return; return;
} }
var html = new StringBuilder(""); // create 'previous' item
var prevIcon = new TagBuilder("span");
html.AppendLine("<a class=\"carousel-control-prev\" href=\"#" + TagHelper.Id + "\" role=\"button\" data-slide=\"prev\">"); prevIcon.AddCssClass("carousel-control-prev-icon");
html.AppendLine("<span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>"); prevIcon.Attributes.Add("aria-hidden", "true");
html.AppendLine("<span class=\"sr-only\">Previous</span>");
html.AppendLine("</a>"); var prevText = new TagBuilder("span");
html.AppendLine("<a class=\"carousel-control-next\" href=\"#" + TagHelper.Id + "\" role=\"button\" data-slide=\"next\">"); prevText.AddCssClass("sr-only");
html.AppendLine("<span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>"); prevText.InnerHtml.Append(L["Previous"].Value);
html.AppendLine("<span class=\"sr-only\">Next</span>");
html.AppendLine("</a>"); var prevAnchor = new TagBuilder("a");
prevAnchor.AddCssClass("carousel-control-prev");
output.PostContent.SetHtmlContent(html.ToString()); prevAnchor.Attributes.Add("href", "#" + TagHelper.Id);
prevAnchor.Attributes.Add("role", "button");
prevAnchor.Attributes.Add("data-slide", "prev");
prevAnchor.InnerHtml.AppendHtml(prevIcon);
prevAnchor.InnerHtml.AppendHtml(prevText);
// create 'next' item
var nextIcon = new TagBuilder("span");
nextIcon.AddCssClass("carousel-control-next-icon");
nextIcon.Attributes.Add("aria-hidden", "true");
var nextText = new TagBuilder("span");
nextText.AddCssClass("sr-only");
nextText.InnerHtml.Append(L["Next"].Value);
var nextAnchor = new TagBuilder("a");
nextAnchor.AddCssClass("carousel-control-next");
nextAnchor.Attributes.Add("href", "#" + TagHelper.Id);
nextAnchor.Attributes.Add("role", "button");
nextAnchor.Attributes.Add("data-slide", "next");
nextAnchor.InnerHtml.AppendHtml(nextIcon);
nextAnchor.InnerHtml.AppendHtml(nextText);
// append post content
output.PostContent.AppendHtml(prevAnchor);
output.PostContent.AppendHtml(nextAnchor);
} }
protected virtual void SetIndicators(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList) protected virtual void SetIndicators(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList)
@ -79,20 +114,24 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
return; return;
} }
var html = new StringBuilder("<ol class=\"carousel-indicators\">"); var list = new TagBuilder("ol");
list.AddCssClass("carousel-indicators");
for (var i = 0; i < itemList.Count; i++) for (var i = 0; i < itemList.Count; i++)
{ {
html.AppendLine( var listItem = new TagBuilder("li");
"<li " + listItem.Attributes.Add("data-target", "#" + TagHelper.Id);
"data-target=\"#" + TagHelper.Id + "\"" + listItem.Attributes.Add("data-slide-to", i.ToString());
" data-slide-to=\"" + i + "\"" +
(itemList[i].Active ? " class=\"active\">" : "") + if (itemList[i].Active)
"</li>"); {
listItem.AddCssClass("active");
}
list.InnerHtml.AppendHtml(listItem);
} }
html.AppendLine("</ol>"); output.PreContent.SetHtmlContent(list);
output.PreContent.SetHtmlContent(html.ToString());
} }
protected virtual void SetOneItemAsActive(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList) protected virtual void SetOneItemAsActive(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList)

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

@ -1,7 +1,8 @@
using System; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse
@ -17,7 +18,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse
var html = GetAccordionHeaderItem(context, output) + GetAccordionContentItem(context, output, innerContent); var html = GetAccordionHeaderItem(context, output) + GetAccordionContentItem(context, output, innerContent);
var tabHeaderItems = context.GetValue<List<string>>(AccordionItems); var tabHeaderItems = context.GetValue<List<string>>(AccordionItems);
tabHeaderItems.Add(html); tabHeaderItems.Add(html);
output.SuppressOutput(); output.SuppressOutput();
@ -25,23 +25,43 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse
protected virtual string GetAccordionHeaderItem(TagHelperContext context, TagHelperOutput output) protected virtual string GetAccordionHeaderItem(TagHelperContext context, TagHelperOutput output)
{ {
return " <div class=\"card-header\" id=\"" + GetHeadingId() + "\">" + Environment.NewLine + var button = new TagBuilder("button");
" <h5 class=\"mb-0\">" + Environment.NewLine + button.AddCssClass("btn btn-link");
" <button class=\"btn btn-link\" type=\"button\" data-toggle=\"collapse\" data-target=\"#" + GetContentId() + "\" aria-expanded=\"true\" aria-controls=\"" + GetContentId() + "\">" + Environment.NewLine + button.Attributes.Add("type", "button");
TagHelper.Title + Environment.NewLine + button.Attributes.Add("data-toggle", "collapse");
" </button>" + Environment.NewLine + button.Attributes.Add("data-target", "#" + GetContentId());
" </h5>" + Environment.NewLine + button.Attributes.Add("aria-expanded", "true");
" </div>" + Environment.NewLine; button.Attributes.Add("aria-controls", GetContentId());
button.InnerHtml.Append(TagHelper.Title);
var h5 = new TagBuilder("h5");
h5.AddCssClass("mb-0");
h5.InnerHtml.AppendHtml(button);
var header = new TagBuilder("div");
header.AddCssClass("card-header");
header.Attributes.Add("id", GetHeadingId());
header.InnerHtml.AppendHtml(h5);
return header.ToHtmlString();
} }
protected virtual string GetAccordionContentItem(TagHelperContext context, TagHelperOutput output, string content) protected virtual string GetAccordionContentItem(TagHelperContext context, TagHelperOutput output, string content)
{ {
var show = (TagHelper.Active ?? false) ? " show" : ""; var show = (TagHelper.Active ?? false) ? " show" : "";
return "<div id=\"" + GetContentId() + "\" class=\"collapse"+ show + "\" aria-labelledby=\"" + GetHeadingId() + "\" data-parent=\"#" + AbpAccordionParentIdPlaceholder + "\">" + Environment.NewLine +
" <div class=\"card-body\">" + Environment.NewLine + var cardBody = new TagBuilder("div");
content + Environment.NewLine + cardBody.AddCssClass("card-body");
" </div>" + Environment.NewLine + cardBody.InnerHtml.AppendHtml(content);
"</div>" + Environment.NewLine;
var wrapper = new TagBuilder("div");
wrapper.AddCssClass("collapse" + show);
wrapper.Attributes.Add("id", GetContentId());
wrapper.Attributes.Add("aria-labelledby", GetHeadingId());
wrapper.Attributes.Add("data-parent", "#" + AbpAccordionParentIdPlaceholder);
wrapper.InnerHtml.AppendHtml(cardBody);
return wrapper.ToHtmlString();
} }
protected virtual string GetHeadingId() protected virtual string GetHeadingId()

35
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionTagHelperService.cs

@ -1,14 +1,22 @@
using System; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse
{ {
public class AbpAccordionTagHelperService : AbpTagHelperService<AbpAccordionTagHelper> public class AbpAccordionTagHelperService : AbpTagHelperService<AbpAccordionTagHelper>
{ {
protected IHtmlGenerator HtmlGenerator { get; }
public AbpAccordionTagHelperService(IHtmlGenerator htmlGenerator)
{
HtmlGenerator = htmlGenerator;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
SetRandomIdIfNotProvided(); SetRandomIdIfNotProvided();
@ -22,26 +30,21 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse
await output.GetChildContentAsync(); await output.GetChildContentAsync();
var content = GetContent(items); SetContent(context, output, items);
output.Content.SetHtmlContent(content);
} }
protected virtual string GetContent(List<string> items) protected virtual void SetContent(TagHelperContext context, TagHelperOutput output, List<string> items)
{ {
var html = new StringBuilder("");
foreach (var item in items) foreach (var item in items)
{ {
var content = item.Replace(AbpAccordionParentIdPlaceholder, TagHelper.Id); var content = item.Replace(AbpAccordionParentIdPlaceholder, HtmlGenerator.Encode(TagHelper.Id));
html.AppendLine( var wrapper = new TagBuilder("div");
"<div class=\"card\">" + Environment.NewLine + wrapper.AddCssClass("card");
content wrapper.InnerHtml.AppendHtml(content);
+ "</div>" + Environment.NewLine
);
}
return html.ToString(); output.Content.AppendHtml(wrapper);
}
} }
protected virtual List<string> InitilizeFormGroupContentsContext(TagHelperContext context, TagHelperOutput output) protected virtual List<string> InitilizeFormGroupContentsContext(TagHelperContext context, TagHelperOutput output)

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

@ -1,12 +1,13 @@
using System; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
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.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
@ -96,7 +97,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual string SurroundInnerHtmlAndGet(TagHelperContext context, TagHelperOutput output, string innerHtml, bool isCheckbox) protected virtual string SurroundInnerHtmlAndGet(TagHelperContext context, TagHelperOutput output, string innerHtml, bool isCheckbox)
{ {
return "<div class=\"" + (isCheckbox ? "custom-checkbox custom-control" : "form-group") + "\">" + return "<div class=\"" + (isCheckbox ? "custom-checkbox custom-control mb-2" : "form-group") + "\">" +
Environment.NewLine + innerHtml + Environment.NewLine + Environment.NewLine + innerHtml + Environment.NewLine +
"</div>"; "</div>";
} }
@ -249,7 +250,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{ {
if (IsOutputHidden(inputTag) || TagHelper.SuppressLabel) if (IsOutputHidden(inputTag) || TagHelper.SuppressLabel)
{ {
return ""; return string.Empty;
} }
if (string.IsNullOrEmpty(TagHelper.Label)) if (string.IsNullOrEmpty(TagHelper.Label))
@ -257,11 +258,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return await GetLabelAsHtmlUsingTagHelperAsync(context, output, isCheckbox) + GetRequiredSymbol(context, output); return await GetLabelAsHtmlUsingTagHelperAsync(context, output, isCheckbox) + GetRequiredSymbol(context, output);
} }
var checkboxClass = isCheckbox ? "class=\"custom-control-label\" " : ""; var label = new TagBuilder("label");
label.Attributes.Add("for", GetIdAttributeValue(inputTag));
label.InnerHtml.Append(TagHelper.Label);
if (isCheckbox)
{
label.AddCssClass("custom-control-label");
}
return "<label " + checkboxClass + GetIdAttributeAsString(inputTag) + ">" return label.ToHtmlString();
+ TagHelper.Label +
"</label>" + GetRequiredSymbol(context, output);
} }
protected virtual string GetRequiredSymbol(TagHelperContext context, TagHelperOutput output) protected virtual string GetRequiredSymbol(TagHelperContext context, TagHelperOutput output)
@ -308,9 +314,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id"); var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer); var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer);
return "<small id=\"" + idAttr?.Value + "InfoText\" class=\"form-text text-muted\">" + var small = new TagBuilder("small");
localizedText + small.Attributes.Add("id", idAttr?.Value?.ToString() + "InfoText");
"</small>"; small.AddCssClass("form-text text-muted");
return small.ToHtmlString();
} }
protected virtual async Task<string> GetLabelAsHtmlUsingTagHelperAsync(TagHelperContext context, TagHelperOutput output, bool isCheckbox) protected virtual async Task<string> GetLabelAsHtmlUsingTagHelperAsync(TagHelperContext context, TagHelperOutput output, bool isCheckbox)
@ -426,11 +434,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return inputTag.Attributes.Any(a => a.Name.ToLowerInvariant() == "type" && a.Value.ToString().ToLowerInvariant() == "hidden"); return inputTag.Attributes.Any(a => a.Name.ToLowerInvariant() == "type" && a.Value.ToString().ToLowerInvariant() == "hidden");
} }
protected virtual string GetIdAttributeAsString(TagHelperOutput inputTag) protected virtual string GetIdAttributeValue(TagHelperOutput inputTag)
{ {
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id"); var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
return idAttr != null ? "for=\"" + idAttr.Value + "\"" : ""; return idAttr != null ? idAttr.Value.ToString() : string.Empty;
}
protected virtual string GetIdAttributeAsString(TagHelperOutput inputTag)
{
var id = GetIdAttributeValue(inputTag);
return !string.IsNullOrWhiteSpace(id) ? "for=\"" + id + "\"" : string.Empty;
} }
protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress)

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

@ -1,12 +1,12 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
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; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
@ -53,15 +53,35 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
var inlineClass = (TagHelper.Inline ?? false) ? " custom-control-inline" : ""; var inlineClass = (TagHelper.Inline ?? false) ? " custom-control-inline" : "";
var id = TagHelper.AspFor.Name + "Radio" + selectItem.Value; var id = TagHelper.AspFor.Name + "Radio" + selectItem.Value;
var name = TagHelper.AspFor.Name; var name = TagHelper.AspFor.Name;
var selected = selectItem.Selected ? " checked=\"checked\"" : "";
var disabled = (TagHelper.Disabled??false) ? " disabled" : "";
var htmlPart = "<div class=\"custom-control custom-radio" + inlineClass + "\">\r\n" + var input = new TagBuilder("input");
" <input type=\"radio\" id=\"" + id + "\" name=\"" + name + "\" value=\"" + selectItem.Value + "\"" + selected + " class=\"custom-control-input\""+ disabled + ">\r\n" + input.AddCssClass("custom-control-input");
" <label class=\"custom-control-label\" for=\"" + id + "\">" + selectItem.Text + "</label>\r\n" + input.Attributes.Add("type", "radio");
"</div>"; input.Attributes.Add("id", id);
input.Attributes.Add("name", name);
input.Attributes.Add("value", selectItem.Value);
if (selectItem.Selected)
{
input.Attributes.Add("checked", "checked");
}
if (TagHelper.Disabled ?? false)
{
input.Attributes.Add("disabled", "disabled");
}
var label = new TagBuilder("label");
label.AddCssClass("custom-control-label");
label.Attributes.Add("for", id);
label.InnerHtml.Append(selectItem.Text);
var wrapper = new TagBuilder("div");
wrapper.AddCssClass("custom-control custom-radio" + inlineClass);
wrapper.InnerHtml.AppendHtml(input);
wrapper.InnerHtml.AppendHtml(label);
html.AppendLine(htmlPart); html.AppendLine(wrapper.ToHtmlString());
} }
return html.ToString(); return html.ToString();

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

@ -1,20 +1,18 @@
using System; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{ {
@ -26,9 +24,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
private readonly IStringLocalizerFactory _stringLocalizerFactory; private readonly IStringLocalizerFactory _stringLocalizerFactory;
public AbpSelectTagHelperService( public AbpSelectTagHelperService(
IHtmlGenerator generator, IHtmlGenerator generator,
HtmlEncoder encoder, HtmlEncoder encoder,
IAbpTagHelperLocalizer tagHelperLocalizer, IAbpTagHelperLocalizer tagHelperLocalizer,
IStringLocalizerFactory stringLocalizerFactory) IStringLocalizerFactory stringLocalizerFactory)
{ {
_generator = generator; _generator = generator;
@ -140,7 +138,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{ {
if (!string.IsNullOrEmpty(TagHelper.Label)) if (!string.IsNullOrEmpty(TagHelper.Label))
{ {
return "<label " + GetIdAttributeAsString(selectTag) + ">" + TagHelper.Label + "</label>" + GetRequiredSymbol(context, output); var label = new TagBuilder("label");
label.Attributes.Add("for", GetIdAttributeValue(selectTag));
label.InnerHtml.Append(TagHelper.Label);
return label.ToHtmlString() + GetRequiredSymbol(context, output);
} }
return await GetLabelAsHtmlUsingTagHelperAsync(context, output) + GetRequiredSymbol(context, output); return await GetLabelAsHtmlUsingTagHelperAsync(context, output) + GetRequiredSymbol(context, output);
@ -199,9 +201,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id"); var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer); var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer);
return "<small id=\"" + idAttr?.Value + "InfoText\" class=\"form-text text-muted\">" + var small = new TagBuilder("small");
localizedText + small.Attributes.Add("id", idAttr?.Value?.ToString() + "InfoText");
"</small>"; small.AddCssClass("form-text text-muted");
return small.ToHtmlString();
} }
protected virtual List<SelectListItem> GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer) protected virtual List<SelectListItem> GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer)
@ -338,11 +342,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
} }
} }
protected virtual string GetIdAttributeAsString(TagHelperOutput inputTag) protected virtual string GetIdAttributeValue(TagHelperOutput inputTag)
{ {
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id"); var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
return idAttr != null ? "for=\"" + idAttr.Value + "\"" : ""; return idAttr != null ? idAttr.Value.ToString() : string.Empty;
}
protected virtual string GetIdAttributeAsString(TagHelperOutput inputTag)
{
var id = GetIdAttributeValue(inputTag);
return !string.IsNullOrWhiteSpace(id) ? "for=\"" + id + "\"" : string.Empty;
} }
protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress)

162
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs

@ -1,60 +1,106 @@
using System.Text; using Localization.Resources.AbpUi;
using Localization.Resources.AbpUi; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using System.Text;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
{ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
public class AbpModalFooterTagHelperService : AbpTagHelperService<AbpModalFooterTagHelper> {
{ public class AbpModalFooterTagHelperService : AbpTagHelperService<AbpModalFooterTagHelper>
private readonly IStringLocalizer<AbpUiResource> _localizer; {
private readonly IStringLocalizer<AbpUiResource> _localizer;
public AbpModalFooterTagHelperService(IStringLocalizer<AbpUiResource> localizer)
{ public AbpModalFooterTagHelperService(IStringLocalizer<AbpUiResource> localizer)
_localizer = localizer; {
} _localizer = localizer;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{ public override void Process(TagHelperContext context, TagHelperOutput output)
output.TagName = "div"; {
output.Attributes.AddClass("modal-footer"); output.TagName = "div";
output.Attributes.AddClass("modal-footer");
if (TagHelper.Buttons != AbpModalButtons.None)
{ if (TagHelper.Buttons != AbpModalButtons.None)
output.PostContent.SetHtmlContent(CreateContent()); {
} output.PostContent.SetHtmlContent(CreateContent());
ProcessButtonsAlignment(output); }
}
ProcessButtonsAlignment(output);
protected virtual string CreateContent() }
{
var sb = new StringBuilder(); protected virtual string CreateContent()
{
switch (TagHelper.Buttons) var sb = new StringBuilder();
{
case AbpModalButtons.Cancel: switch (TagHelper.Buttons)
sb.AppendLine("<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">" + _localizer["Cancel"] + "</button>"); {
break; case AbpModalButtons.Cancel:
case AbpModalButtons.Close: sb.AppendLine(GetCancelButton());
sb.AppendLine("<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">" + _localizer["Close"] + "</button>"); break;
break; case AbpModalButtons.Close:
case AbpModalButtons.Save: sb.AppendLine(GetCloseButton());
sb.AppendLine("<button type=\"submit\" class=\"btn btn-primary\" data-busy-text=\"" + _localizer["SavingWithThreeDot"] + "\"><i class=\"fa fa-check\"></i> <span>" + _localizer["Save"] + "</span></button>"); break;
break; case AbpModalButtons.Save:
case AbpModalButtons.Save | AbpModalButtons.Cancel: sb.AppendLine(GetSaveButton());
sb.AppendLine("<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">" + _localizer["Cancel"] + "</button>"); break;
sb.AppendLine("<button type=\"submit\" class=\"btn btn-primary\" data-busy-text=\"" + _localizer["SavingWithThreeDot"] + "\"><i class=\"fa fa-check\"></i> <span>" + _localizer["Save"] + "</span></button>"); case AbpModalButtons.Save | AbpModalButtons.Cancel:
break; sb.AppendLine(GetSaveButton());
case AbpModalButtons.Save | AbpModalButtons.Close: sb.AppendLine(GetCancelButton());
sb.AppendLine("<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">" + _localizer["Close"] + "</button>"); break;
sb.AppendLine("<button type=\"submit\" class=\"btn btn-primary\" data-busy-text=\"" + _localizer["SavingWithThreeDot"] + "\"><i class=\"fa fa-check\"></i> <span>" + _localizer["Save"] + "</span></button>"); case AbpModalButtons.Save | AbpModalButtons.Close:
break; sb.AppendLine(GetSaveButton());
sb.AppendLine(GetCloseButton());
break;
} }
return sb.ToString(); return sb.ToString();
} }
protected virtual string GetSaveButton()
{
var icon = new TagBuilder("i");
icon.AddCssClass("fa");
icon.AddCssClass("fa-check");
var span = new TagBuilder("span");
span.InnerHtml.Append(_localizer["Save"]);
var element = new TagBuilder("button");
element.Attributes.Add("type", "submit");
element.AddCssClass("btn");
element.AddCssClass("btn-primary");
element.Attributes.Add("data-busy-text", _localizer["SavingWithThreeDot"]);
element.InnerHtml.AppendHtml(icon);
element.InnerHtml.AppendHtml(span);
return element.ToHtmlString();
}
protected virtual string GetCloseButton()
{
var element = new TagBuilder("button");
element.Attributes.Add("type", "button");
element.Attributes.Add("data-dismiss", "modal");
element.AddCssClass("btn");
element.AddCssClass("btn-secondary");
element.InnerHtml.Append(_localizer["Close"]);
return element.ToHtmlString();
}
protected virtual string GetCancelButton()
{
var element = new TagBuilder("button");
element.Attributes.Add("type", "button");
element.Attributes.Add("data-dismiss", "modal");
element.AddCssClass("btn");
element.AddCssClass("btn-secondary");
element.InnerHtml.Append(_localizer["Cancel"]);
return element.ToHtmlString();
}
protected virtual void ProcessButtonsAlignment(TagHelperOutput output) protected virtual void ProcessButtonsAlignment(TagHelperOutput output)
{ {
if (TagHelper.ButtonAlignment == ButtonsAlign.Default) if (TagHelper.ButtonAlignment == ButtonsAlign.Default)
@ -62,6 +108,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
return; return;
} }
output.Attributes.AddClass("justify-content-" + TagHelper.ButtonAlignment.ToString().ToLowerInvariant()); output.Attributes.AddClass("justify-content-" + TagHelper.ButtonAlignment.ToString().ToLowerInvariant());
} }
} }
} }

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

@ -1,11 +1,20 @@
using System.Text; using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers; 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.Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
{ {
public class AbpModalHeaderTagHelperService : AbpTagHelperService<AbpModalHeaderTagHelper> public class AbpModalHeaderTagHelperService : AbpTagHelperService<AbpModalHeaderTagHelper>
{ {
protected IStringLocalizer<AbpUiResource> L { get; }
public AbpModalHeaderTagHelperService(IStringLocalizer<AbpUiResource> localizer)
{
L = localizer;
}
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
output.TagName = "div"; output.TagName = "div";
@ -16,22 +25,27 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
protected virtual string CreatePreContent() protected virtual string CreatePreContent()
{ {
var sb = new StringBuilder(); var title = new TagBuilder("h5");
title.AddCssClass("modal-title");
sb.AppendLine(" <h5 class=\"modal-title\">" + TagHelper.Title + "</h5>"); title.InnerHtml.Append(TagHelper.Title);
return sb.ToString(); return title.ToHtmlString();
} }
protected virtual string CreatePostContent() protected virtual string CreatePostContent()
{ {
var sb = new StringBuilder(); var span = new TagBuilder("span");
span.Attributes.Add("aria-hidden", "true");
sb.AppendLine(" <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">"); span.InnerHtml.AppendHtml("&times;");
sb.AppendLine(" <span aria-hidden=\"true\">&times;</span>");
sb.AppendLine(" </button>"); var button = new TagBuilder("button");
button.AddCssClass("close");
button.Attributes.Add("type", "button");
button.Attributes.Add("data-dismiss", "modal");
button.Attributes.Add("aria-label", L["Close"].Value);
button.InnerHtml.AppendHtml(span);
return sb.ToString(); return button.ToHtmlString();
} }
} }
} }

87
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalTagHelperService.cs

@ -1,30 +1,66 @@
using System.Linq; using Microsoft.AspNetCore.Html;
using System.Text; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Text;
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
{ {
public class AbpModalTagHelperService : AbpTagHelperService<AbpModalTagHelper> public class AbpModalTagHelperService : AbpTagHelperService<AbpModalTagHelper>
{ {
public override void Process(TagHelperContext context, TagHelperOutput output) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
output.TagName = null; output.TagName = null;
output.PreContent.SetHtmlContent(CreatePreContent(output));
output.PostContent.SetHtmlContent(CreatePostContent());
}
protected virtual string CreatePreContent(TagHelperOutput output) var childContent = await output.GetChildContentAsync();
{
var sb = new StringBuilder(); SetContent(context, output, childContent);
}
var attritubutes = output.Attributes.Select(a => " " + a.Name + "=\"" + a.Value + "\" ").ToList(); protected virtual void SetContent(TagHelperContext context, TagHelperOutput output, TagHelperContent childContent)
var attritubutesAsJoin = string.Join(" ", attritubutes.ToArray()); {
var modalContent = GetModalContentElement(context, output, childContent);
var modalDialog = GetModalDialogElement(context, output, modalContent);
var modal = GetModal(context, output, modalDialog);
sb.AppendLine("<div class=\"" + GetModalClasses() + "\" tabindex=\"-1\" role=\"dialog\" aria-hidden=\"true\" " + attritubutesAsJoin + GetDataAttributes() + ">"); output.Content.SetHtmlContent(modal);
sb.AppendLine(" <div class=\"" + GetModalDialogClasses() + "\" role=\"document\">"); }
sb.AppendLine(" <div class=\"" + GetModalContentClasses() + "\">");
protected virtual TagBuilder GetModalContentElement(TagHelperContext context, TagHelperOutput output, TagHelperContent childContent)
{
var element = new TagBuilder("div");
element.AddCssClass(GetModalContentClasses());
element.InnerHtml.SetHtmlContent(childContent);
return element;
}
return sb.ToString(); protected virtual TagBuilder GetModalDialogElement(TagHelperContext context, TagHelperOutput output, IHtmlContent innerHtml)
{
var element = new TagBuilder("div");
element.AddCssClass(GetModalDialogClasses());
element.Attributes.Add("role", "document");
element.InnerHtml.SetHtmlContent(innerHtml);
return element;
}
protected virtual TagBuilder GetModal(TagHelperContext context, TagHelperOutput output, IHtmlContent innerHtml)
{
var element = new TagBuilder("div");
element.AddCssClass(GetModalClasses());
element.Attributes.Add("tabindex", "-1");
element.Attributes.Add("role", "dialog");
element.Attributes.Add("aria-hidden", "true");
foreach (var attr in output.Attributes)
{
element.Attributes.Add(attr.Name, attr.Value.ToString());
}
SetDataAttributes(element);
element.InnerHtml.SetHtmlContent(innerHtml);
return element;
} }
protected virtual string GetModalClasses() protected virtual string GetModalClasses()
@ -63,17 +99,14 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
return "data-backdrop=\"static\" "; return "data-backdrop=\"static\" ";
} }
return string.Empty; return string.Empty;
} }
protected virtual string CreatePostContent() protected virtual void SetDataAttributes(TagBuilder builder)
{ {
var sb = new StringBuilder(); if (TagHelper.Static == true)
{
sb.AppendLine(" </div>"); builder.Attributes.Add("data-backdrop", "static");
sb.AppendLine(" </div>"); }
sb.AppendLine("</div>");
return sb.ToString();
} }
} }
} }

30
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Nav/AbpNavbarToggleTagHelperService.cs

@ -1,11 +1,21 @@
using System; using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using System;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Nav namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Nav
{ {
public class AbpNavbarToggleTagHelperService : AbpTagHelperService<AbpNavbarToggleTagHelper> public class AbpNavbarToggleTagHelperService : AbpTagHelperService<AbpNavbarToggleTagHelper>
{ {
protected IStringLocalizer<AbpUiResource> L { get; }
public AbpNavbarToggleTagHelperService(IStringLocalizer<AbpUiResource> stringLocalizer)
{
L = stringLocalizer;
}
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
SetRandomNameIfNotProvided(); SetRandomNameIfNotProvided();
@ -18,10 +28,20 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Nav
protected virtual void SetToggleButton(TagHelperContext context, TagHelperOutput output) protected virtual void SetToggleButton(TagHelperContext context, TagHelperOutput output)
{ {
output.PreElement.SetHtmlContent( var span = new TagBuilder("span");
"<button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#" + span.AddCssClass("navbar-toggler-icon");
TagHelper.Id + "\" aria-controls=\"" + TagHelper.Id +
"\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>"); var button = new TagBuilder("button");
button.AddCssClass("navbar-toggler");
button.Attributes.Add("type", "button");
button.Attributes.Add("data-toggle", "collapse");
button.Attributes.Add("data-target", "#" + TagHelper.Id);
button.Attributes.Add("aria-controls", TagHelper.Id);
button.Attributes.Add("aria-expanded", "false");
button.Attributes.Add("aria-label", L["ToggleNavigation"].Value);
button.InnerHtml.AppendHtml(span);
output.PreElement.SetHtmlContent(button);
} }
protected virtual void SetRandomNameIfNotProvided() protected virtual void SetRandomNameIfNotProvided()

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

@ -1,13 +1,13 @@
using System; using Localization.Resources.AbpUi;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Mvc.TagHelpers; using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using System;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
@ -134,7 +134,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
SetHrefAttribute(currentPage, attributeList); SetHrefAttribute(currentPage, attributeList);
tagHelperOutput.Content.SetHtmlContent(localizer[localizationKey]); tagHelperOutput.Content.SetContent(localizer[localizationKey]);
var renderedHtml = tagHelperOutput.Render(_encoder); var renderedHtml = tagHelperOutput.Render(_encoder);
@ -166,7 +166,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
var localizer = _stringLocalizerFactory.Create(typeof(AbpUiResource)); var localizer = _stringLocalizerFactory.Create(typeof(AbpUiResource));
var pagerInfo = (TagHelper.ShowInfo ?? false) ? var pagerInfo = (TagHelper.ShowInfo ?? false) ?
" <div class=\"col-sm-12 col-md-5\"> " + localizer["PagerInfo{0}{1}{2}", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount] + "</div>\r\n" " <div class=\"col-sm-12 col-md-5\"> " + _encoder.Encode(localizer["PagerInfo{0}{1}{2}", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount]) + "</div>\r\n"
: ""; : "";
return return

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

@ -1,11 +1,20 @@
using System; using Microsoft.AspNetCore.Mvc.Rendering;
using System.Linq; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Linq;
using System.Web;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover
{ {
public class AbpPopoverTagHelperService : AbpTagHelperService<AbpPopoverTagHelper> public class AbpPopoverTagHelperService : AbpTagHelperService<AbpPopoverTagHelper>
{ {
protected IHtmlGenerator HtmlGenerator { get; }
public AbpPopoverTagHelperService(IHtmlGenerator htmlGenerator)
{
HtmlGenerator = htmlGenerator;
}
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
if (!TagHelper.Disabled ?? true) if (!TagHelper.Disabled ?? true)
@ -21,38 +30,51 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover
SetDisabled(context, output); SetDisabled(context, output);
} }
} }
protected virtual void SetDisabled(TagHelperContext context, TagHelperOutput output) protected virtual void SetDisabled(TagHelperContext context, TagHelperOutput output)
{ {
var triggerAsHtml = TagHelper.Dismissible ?? false ? "data-trigger=\"focus\" " : ""; var triggerValue = TagHelper.Dismissible ?? false ? "focus" : "";
if (TagHelper.Hoverable ?? false) if (TagHelper.Hoverable ?? false)
{ {
if (triggerAsHtml.Contains("focus")) if (triggerValue.Contains("focus"))
{ {
triggerAsHtml = triggerAsHtml.Replace("focus", "focus hover"); triggerValue = triggerValue.Replace("focus", "focus hover");
} }
else else
{ {
triggerAsHtml = "data-trigger=\"hover\" "; triggerValue = "hover";
} }
} }
var dataPlacementAsHtml = "data-placement=\"" + GetDirectory().ToString().ToLowerInvariant() + "\" ";
var dataPlacement = GetDirectory().ToString().ToLowerInvariant();
// data-placement="default" with data-trigger="focus" causes Cannot read property 'indexOf' of undefined at computeAutoPlacement(bootstrap.bundle.js?_v=637146714627330435:2185) error // data-placement="default" with data-trigger="focus" causes Cannot read property 'indexOf' of undefined at computeAutoPlacement(bootstrap.bundle.js?_v=637146714627330435:2185) error
if (IsDismissibleOrHoverable() && GetDirectory() == PopoverDirectory.Default) if (IsDismissibleOrHoverable() && GetDirectory() == PopoverDirectory.Default)
{ {
//dataPlacementAsHtml = string.Empty; //bootstrap default placement is right, abp's is top. //dataPlacementAsHtml = string.Empty; //bootstrap default placement is right, abp's is top.
dataPlacementAsHtml = dataPlacementAsHtml.Replace("default", "top"); dataPlacement = dataPlacement.Replace("default", "top");
} }
var titleAttribute = output.Attributes.FirstOrDefault(at => at.Name == "title"); var titleAttribute = output.Attributes.FirstOrDefault(at => at.Name == "title");
var titleAsHtml = titleAttribute == null ? "" : "title=\"" + titleAttribute.Value + "\" ";
var preElementHtml = "<span tabindex=\"0\" class=\"d-inline-block\" " + titleAsHtml + triggerAsHtml + dataPlacementAsHtml + "data-toggle=\"popover\" data-content=\"" + GetDataContent() + "\">";
var postElementHtml = "</span>";
output.PreElement.SetHtmlContent(preElementHtml); var span = new TagBuilder("span");
output.PostElement.SetHtmlContent(postElementHtml); span.AddCssClass("d-inline-block");
span.Attributes.Add("tabindex", "0");
span.Attributes.Add("data-toggle", "popover");
span.Attributes.Add("data-content", GetDataContent());
span.Attributes.Add("data-trigger", triggerValue);
span.Attributes.Add("data-placement", dataPlacement);
if (titleAttribute != null)
{
span.Attributes.Add("title", HttpUtility.HtmlDecode(titleAttribute.Value.ToString()));
}
output.PreElement.SetHtmlContent(span.RenderStartTag());
output.PostElement.SetHtmlContent(span.RenderEndTag());
output.Attributes.Add("style", "pointer-events: none;"); output.Attributes.Add("style", "pointer-events: none;");
} }
protected virtual void SetDataTriggerIfDismissible(TagHelperContext context, TagHelperOutput output) protected virtual void SetDataTriggerIfDismissible(TagHelperContext context, TagHelperOutput output)
{ {
if (TagHelper.Dismissible ?? false) if (TagHelper.Dismissible ?? false)

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

@ -1,7 +1,8 @@
using System; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
@ -31,14 +32,26 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
var link = TagHelper.Name; var link = TagHelper.Name;
var title = TagHelper.Title; var title = TagHelper.Title;
return "<li class=\"nav-item dropdown\">" + var anchor = new TagBuilder("a");
"<a class=\"nav-link dropdown-toggle\" id=\"" + id + "\" data-toggle=\"dropdown\" href=\"#" + link + "\" role=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\">" + anchor.AddCssClass("nav-link dropdown-toggle");
title + anchor.Attributes.Add("id", id);
"</a>" + anchor.Attributes.Add("data-toggle", "dropdown");
"<div class=\"dropdown-menu\">" + anchor.Attributes.Add("href", "#" + link);
AbpTabDropdownItemsActivePlaceholder + anchor.Attributes.Add("role", "button");
"</div>" + anchor.Attributes.Add("aria-haspopup", "true");
"</li>"; anchor.Attributes.Add("aria-expanded", "false");
anchor.InnerHtml.Append(title);
var menu = new TagBuilder("div");
menu.AddCssClass("dropdown-menu");
menu.InnerHtml.Append(AbpTabDropdownItemsActivePlaceholder);
var listItem = new TagBuilder("li");
listItem.AddCssClass("nav-item dropdown");
listItem.InnerHtml.AppendHtml(anchor);
listItem.InnerHtml.AppendHtml(menu);
return listItem.ToHtmlString();
} }
} }
} }

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

@ -1,6 +1,7 @@
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
@ -10,7 +11,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
SetPlaceholderForNameIfNotProvided(); SetPlaceholderForNameIfNotProvided();
var tabHeader = GetTabHeaderItem(context, output); var tabHeader = GetTabHeaderItem(context, output);
var tabHeaderItems = context.GetValue<List<TabItem>>(TabItems); var tabHeaderItems = context.GetValue<List<TabItem>>(TabItems);
@ -30,12 +31,28 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
if (!string.IsNullOrWhiteSpace(TagHelper.ParentDropdownName)) if (!string.IsNullOrWhiteSpace(TagHelper.ParentDropdownName))
{ {
return "<a class=\"dropdown-item\" id=\"" + id + "\" href=\"" + href + "\">" + title + "</a>"; var anchor = new TagBuilder("a");
anchor.AddCssClass("dropdown-item");
anchor.Attributes.Add("id", id);
anchor.Attributes.Add("href", href);
anchor.InnerHtml.Append(title);
return anchor.ToHtmlString();
} }
else
{
var anchor = new TagBuilder("a");
anchor.AddCssClass("nav-link " + AbpTabItemActivePlaceholder);
anchor.Attributes.Add("id", id);
anchor.Attributes.Add("href", href);
anchor.InnerHtml.Append(title);
var listItem = new TagBuilder("li");
listItem.AddCssClass("nav-item");
listItem.InnerHtml.AppendHtml(anchor);
return "<li class=\"nav-item\"><a class=\"nav-link" + AbpTabItemActivePlaceholder + "\" id=\"" + id + "\" href=\"" + href + "\">" + return listItem.ToHtmlString();
title + }
"</a></li>";
} }
protected virtual void SetPlaceholderForNameIfNotProvided() protected virtual void SetPlaceholderForNameIfNotProvided()
@ -46,4 +63,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
} }
} }
} }
} }

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

@ -1,7 +1,8 @@
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
@ -34,16 +35,52 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
var attributes = GetTabHeaderAttributes(context, output); var attributes = GetTabHeaderAttributes(context, output);
var classAttributesAsString = attributes.Where(a=>a.Name == "class").ToList().Select(a=>a.Value).JoinAsString(" "); var classAttributesAsString = attributes.Where(a=>a.Name == "class").ToList().Select(a=>a.Value).JoinAsString(" ");
var otherAttributesAsString = attributes.Where(a => a.Name != "class").ToList().ToHtmlAttributesAsString(); var otherAttributes = attributes.Where(a => a.Name != "class").ToList();
if (!string.IsNullOrWhiteSpace(TagHelper.ParentDropdownName)) if (!string.IsNullOrWhiteSpace(TagHelper.ParentDropdownName))
{ {
return "<a class=\"dropdown-item "+ classAttributesAsString + "\" id=\"" + id + "\" href=\"#" + link + "\" data-toggle=\"tab\" role=\"tab\" aria-controls=\"" + control + "\" aria-selected=\"false\" "+ otherAttributesAsString + ">" + title + "</a>"; var anchor = new TagBuilder("a");
anchor.AddCssClass("dropdown-item " + classAttributesAsString);
anchor.Attributes.Add("id", id);
anchor.Attributes.Add("href", "#" + link);
anchor.Attributes.Add("data-toggle", "tab");
anchor.Attributes.Add("role", "tab");
anchor.Attributes.Add("aria-controls", control);
anchor.Attributes.Add("aria-selected", "false");
foreach (var attr in otherAttributes)
{
anchor.Attributes.Add(attr.Name, attr.Value.ToString());
}
anchor.InnerHtml.Append(title);
return anchor.ToHtmlString();
}
else
{
var anchor = new TagBuilder("a");
anchor.AddCssClass("nav-link " + classAttributesAsString + " " + AbpTabItemActivePlaceholder);
anchor.Attributes.Add("id", id);
anchor.Attributes.Add("data-toggle", TabItemsDataTogglePlaceHolder);
anchor.Attributes.Add("href", "#" + link);
anchor.Attributes.Add("role", "tab");
anchor.Attributes.Add("aria-controls", control);
anchor.Attributes.Add("aria-selected", AbpTabItemSelectedPlaceholder);
foreach (var attr in otherAttributes)
{
anchor.Attributes.Add(attr.Name, attr.Value.ToString());
}
anchor.InnerHtml.Append(title);
var listItem = new TagBuilder("li");
listItem.AddCssClass("nav-item");
listItem.InnerHtml.AppendHtml(anchor);
return listItem.ToHtmlString();
} }
return "<li class=\"nav-item\"><a class=\"nav-link " + classAttributesAsString + " " + AbpTabItemActivePlaceholder + "\" id=\"" + id + "\" data-toggle=\"" + TabItemsDataTogglePlaceHolder + "\" href=\"#" + link + "\" role=\"tab\" aria-controls=\"" + control + "\" aria-selected=\"" + AbpTabItemSelectedPlaceholder + "\" "+ otherAttributesAsString + ">" +
title +
"</a></li>";
} }
protected virtual string GetTabContentItem(TagHelperContext context, TagHelperOutput output, string content) protected virtual string GetTabContentItem(TagHelperContext context, TagHelperOutput output, string content)
@ -53,11 +90,22 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
var attributes = GetTabContentAttributes(context, output); var attributes = GetTabContentAttributes(context, output);
var classAttributesAsString = attributes.Where(a => a.Name == "class").ToList().Select(a => a.Name).JoinAsString(" "); var classAttributesAsString = attributes.Where(a => a.Name == "class").ToList().Select(a => a.Name).JoinAsString(" ");
var otherAttributesAsString = attributes.Where(a => a.Name != "class").ToList().ToHtmlAttributesAsString(); var otherAttributes = attributes.Where(a => a.Name != "class").ToList();
var wrapper = new TagBuilder("div");
wrapper.AddCssClass("tab-pane fade " + classAttributesAsString + " " + AbpTabItemShowActivePlaceholder);
wrapper.Attributes.Add("id", id);
wrapper.Attributes.Add("role", "tabpanel");
wrapper.Attributes.Add("aria-labelledby", headerId);
foreach (var attr in otherAttributes)
{
wrapper.Attributes.Add(attr.Name, attr.Value.ToString());
}
wrapper.InnerHtml.AppendHtml(content);
return "<div class=\"tab-pane fade " + classAttributesAsString + " " + AbpTabItemShowActivePlaceholder + "\" id=\"" + id + "\" role=\"tabpanel\" aria-labelledby=\"" + headerId + "\" " + otherAttributesAsString + ">" + return wrapper.ToHtmlString();
content +
"</div>";
} }
protected virtual void SetPlaceholderForNameIfNotProvided() protected virtual void SetPlaceholderForNameIfNotProvided()
@ -83,4 +131,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
.Select(a=> new TagHelperAttribute(a.Name.Substring(prefix.Length), a.Value)).ToList(); .Select(a=> new TagHelperAttribute(a.Name.Substring(prefix.Length), a.Value)).ToList();
} }
} }
} }

46
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabsTagHelperService.cs

@ -1,9 +1,11 @@
using System; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid;
@ -11,6 +13,13 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
{ {
public class AbpTabsTagHelperService : AbpTagHelperService<AbpTabsTagHelper> public class AbpTabsTagHelperService : AbpTagHelperService<AbpTabsTagHelper>
{ {
protected IHtmlGenerator HtmlGenerator { get; }
public AbpTabsTagHelperService(IHtmlGenerator htmlGenerator)
{
HtmlGenerator = htmlGenerator;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
SetRandomNameIfNotProvided(); SetRandomNameIfNotProvided();
@ -35,7 +44,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
} }
} }
protected virtual string CombineHeadersAndContents(TagHelperContext context, TagHelperOutput output, string headers, string contents) protected virtual string CombineHeadersAndContents(TagHelperContext context, TagHelperOutput output, string headers, string contents)
{ {
var combined = new StringBuilder(); var combined = new StringBuilder();
@ -61,31 +70,34 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
var navClass = TagHelper.TabStyle == TabStyle.Tab ? " nav-tabs" : " nav-pills"; var navClass = TagHelper.TabStyle == TabStyle.Tab ? " nav-tabs" : " nav-pills";
var verticalClass = GetVerticalPillClassIfVertical(); var verticalClass = GetVerticalPillClassIfVertical();
var surroundedHeaders = "<ul class=\"nav" + verticalClass + navClass + "\" id=\"" + id + "\" role=\"tablist\">" + Environment.NewLine + var listElement = new TagBuilder("ul");
headers + listElement.AddCssClass("nav" + verticalClass + navClass);
"</ul>"; listElement.Attributes.Add("id", id);
listElement.Attributes.Add("role", "tablist");
listElement.InnerHtml.AppendHtml(headers);
return surroundedHeaders; return listElement.ToHtmlString();
} }
protected virtual string SurroundContents(TagHelperContext context, TagHelperOutput output, string contents) protected virtual string SurroundContents(TagHelperContext context, TagHelperOutput output, string contents)
{ {
var id = TagHelper.Name + "Content"; var id = TagHelper.Name + "Content";
var surroundedContents = "<div class=\"tab-content\" id=\"" + id + "\">" + Environment.NewLine + var wrapper = new TagBuilder("div");
contents + wrapper.AddCssClass("tab-content");
" </div>"; wrapper.Attributes.Add("id", id);
wrapper.InnerHtml.AppendHtml(contents);
return surroundedContents; return wrapper.ToHtmlString();
} }
protected virtual string PlaceInsideColumn(string contents, int columnSize) protected virtual string PlaceInsideColumn(string contents, int columnSize)
{ {
var surroundedContents = "<div class=\"col-md-" + columnSize + "\">" + Environment.NewLine + var wrapper = new TagBuilder("div");
contents + wrapper.AddCssClass("col-md-" + columnSize);
" </div>"; wrapper.InnerHtml.AppendHtml(contents);
return surroundedContents; return wrapper.ToHtmlString();
} }
protected virtual void PlaceInsideRow(TagHelperOutput output) protected virtual void PlaceInsideRow(TagHelperOutput output)
@ -213,7 +225,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
protected virtual string SetTabItemNameIfNotProvided(string content, int index) protected virtual string SetTabItemNameIfNotProvided(string content, int index)
{ {
return content.Replace(TabItemNamePlaceHolder, TagHelper.Name + "_" + index); return content.Replace(TabItemNamePlaceHolder, HtmlGenerator.Encode(TagHelper.Name) + "_" + index);
} }
} }
} }

23
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/TagBuilderExtensions.cs

@ -0,0 +1,23 @@
using System.IO;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers
{
internal static class TagBuilderExtensions
{
public static string ToHtmlString(this TagBuilder tagBuilder)
{
return tagBuilder.ToHtmlString(HtmlEncoder.Default);
}
public static string ToHtmlString(this TagBuilder tagBuilder, HtmlEncoder htmlEncoder)
{
using (var writer = new StringWriter())
{
tagBuilder.WriteTo(writer, htmlEncoder);
return writer.ToString();
}
}
}
}

18
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tooltip/AbpTooltipTagHelperService.cs

@ -1,6 +1,6 @@
using System; using Microsoft.AspNetCore.Mvc.Rendering;
using System.Linq;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Linq;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tooltip namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tooltip
{ {
@ -24,12 +24,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tooltip
var directory = GetDirectory() != TooltipDirectory.Default ? GetDirectory() : TooltipDirectory.Top; var directory = GetDirectory() != TooltipDirectory.Default ? GetDirectory() : TooltipDirectory.Top;
output.Attributes.Add("data-placement", directory.ToString().ToLowerInvariant()); output.Attributes.Add("data-placement", directory.ToString().ToLowerInvariant());
output.PreElement.SetHtmlContent( var span = new TagBuilder("span");
"<span class=\"d-inline-block\" tabindex=\"0\" data-toggle=\"tooltip\" " + span.AddCssClass("d-inline-block");
"data-placement=\"" + directory.ToString().ToLowerInvariant() + span.Attributes.Add("tabindex", "0");
"\" title=\"" + GetTitle() + "\">" + Environment.NewLine); span.Attributes.Add("data-toggle", "tooltip");
span.Attributes.Add("data-placement", directory.ToString().ToLowerInvariant());
span.Attributes.Add("title", GetTitle());
output.PreElement.SetHtmlContent(span.RenderStartTag());
output.PostElement.SetHtmlContent(Environment.NewLine + "</span>"); output.PostElement.SetHtmlContent(span.RenderEndTag());
output.Attributes.Add("style", "pointer-events: none;"); output.Attributes.Add("style", "pointer-events: none;");
} }

9
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs

@ -19,18 +19,21 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers
private readonly IStringLocalizer<AbpUiResource> _localizer; private readonly IStringLocalizer<AbpUiResource> _localizer;
private readonly AbpErrorPageOptions _abpErrorPageOptions; private readonly AbpErrorPageOptions _abpErrorPageOptions;
private readonly IExceptionNotifier _exceptionNotifier; private readonly IExceptionNotifier _exceptionNotifier;
private readonly AbpExceptionHandlingOptions _exceptionHandlingOptions;
public ErrorController( public ErrorController(
IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, IExceptionToErrorInfoConverter exceptionToErrorInfoConverter,
IHttpExceptionStatusCodeFinder httpExceptionStatusCodeFinder, IHttpExceptionStatusCodeFinder httpExceptionStatusCodeFinder,
IOptions<AbpErrorPageOptions> abpErrorPageOptions, IOptions<AbpErrorPageOptions> abpErrorPageOptions,
IStringLocalizer<AbpUiResource> localizer, IStringLocalizer<AbpUiResource> localizer,
IExceptionNotifier exceptionNotifier) IExceptionNotifier exceptionNotifier,
IOptions<AbpExceptionHandlingOptions> exceptionHandlingOptions)
{ {
_errorInfoConverter = exceptionToErrorInfoConverter; _errorInfoConverter = exceptionToErrorInfoConverter;
_statusCodeFinder = httpExceptionStatusCodeFinder; _statusCodeFinder = httpExceptionStatusCodeFinder;
_localizer = localizer; _localizer = localizer;
_exceptionNotifier = exceptionNotifier; _exceptionNotifier = exceptionNotifier;
_exceptionHandlingOptions = exceptionHandlingOptions.Value;
_abpErrorPageOptions = abpErrorPageOptions.Value; _abpErrorPageOptions = abpErrorPageOptions.Value;
} }
@ -44,7 +47,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers
await _exceptionNotifier.NotifyAsync(new ExceptionNotificationContext(exception)); await _exceptionNotifier.NotifyAsync(new ExceptionNotificationContext(exception));
var errorInfo = _errorInfoConverter.Convert(exception); var errorInfo = _errorInfoConverter.Convert(exception, _exceptionHandlingOptions.SendExceptionsDetailsToClients);
if (httpStatusCode == 0) if (httpStatusCode == 0)
{ {

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

@ -27,6 +27,10 @@
} }
}; };
var htmlEncode = function (html) {
return $('<div/>').text(html).html();
}
var _createDropdownItem = function (record, fieldItem, tableInstance) { var _createDropdownItem = function (record, fieldItem, tableInstance) {
var $li = $('<li/>'); var $li = $('<li/>');
var $a = $('<a/>'); var $a = $('<a/>');
@ -41,7 +45,7 @@
$a.append($("<i>").addClass(fieldItem.iconClass + " mr-1")); $a.append($("<i>").addClass(fieldItem.iconClass + " mr-1"));
} }
$a.append(fieldItem.text); $a.append(htmlEncode(fieldItem.text));
} }
if (fieldItem.action) { if (fieldItem.action) {
@ -84,7 +88,7 @@
} else if (firstItem.iconClass) { } else if (firstItem.iconClass) {
$button.append($("<i>").addClass(firstItem.iconClass + " mr-1")); $button.append($("<i>").addClass(firstItem.iconClass + " mr-1"));
} }
$button.append(firstItem.text); $button.append(htmlEncode(firstItem.text));
} }
if (firstItem.enabled && !firstItem.enabled({ record: record, table: tableInstance })) { if (firstItem.enabled && !firstItem.enabled({ record: record, table: tableInstance })) {
@ -128,9 +132,9 @@
} }
if (field.text) { if (field.text) {
$dropdownButton.append(field.text); $dropdownButton.append(htmlEncode(fieldItem.text));
} else { } else {
$dropdownButton.append(localize("DatatableActionDropdownDefaultText")); $dropdownButton.append(htmlEncode(localize("DatatableActionDropdownDefaultText")));
} }
$dropdownButton $dropdownButton

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

@ -35,7 +35,12 @@
//change text //change text
if ($buttonInnerSpan.length && $button.attr('data-busy-text')) { if ($buttonInnerSpan.length && $button.attr('data-busy-text')) {
$button.data('buttonOriginalText', $buttonInnerSpan.html()); $button.data('buttonOriginalText', $buttonInnerSpan.html());
$buttonInnerSpan.html($button.attr('data-busy-text'));
if ($button.data('busy-text-is-html')) {
$buttonInnerSpan.html($button.attr('data-busy-text'));
} else {
$buttonInnerSpan.text($button.attr('data-busy-text'));
}
} }
$button.addClass('button-busy'); $button.addClass('button-busy');

8
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
@ -23,15 +24,18 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
private readonly IExceptionToErrorInfoConverter _errorInfoConverter; private readonly IExceptionToErrorInfoConverter _errorInfoConverter;
private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder; private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly AbpExceptionHandlingOptions _exceptionHandlingOptions;
public AbpExceptionFilter( public AbpExceptionFilter(
IExceptionToErrorInfoConverter errorInfoConverter, IExceptionToErrorInfoConverter errorInfoConverter,
IHttpExceptionStatusCodeFinder statusCodeFinder, IHttpExceptionStatusCodeFinder statusCodeFinder,
IJsonSerializer jsonSerializer) IJsonSerializer jsonSerializer,
IOptions<AbpExceptionHandlingOptions> exceptionHandlingOptions)
{ {
_errorInfoConverter = errorInfoConverter; _errorInfoConverter = errorInfoConverter;
_statusCodeFinder = statusCodeFinder; _statusCodeFinder = statusCodeFinder;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_exceptionHandlingOptions = exceptionHandlingOptions.Value;
Logger = NullLogger<AbpExceptionFilter>.Instance; Logger = NullLogger<AbpExceptionFilter>.Instance;
} }
@ -76,7 +80,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true");
context.HttpContext.Response.StatusCode = (int)_statusCodeFinder.GetStatusCode(context.HttpContext, context.Exception); context.HttpContext.Response.StatusCode = (int)_statusCodeFinder.GetStatusCode(context.HttpContext, context.Exception);
var remoteServiceErrorInfo = _errorInfoConverter.Convert(context.Exception); var remoteServiceErrorInfo = _errorInfoConverter.Convert(context.Exception, _exceptionHandlingOptions.SendExceptionsDetailsToClients);
context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo)); context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo));

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

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
@ -23,15 +24,18 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
private readonly IExceptionToErrorInfoConverter _errorInfoConverter; private readonly IExceptionToErrorInfoConverter _errorInfoConverter;
private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder; private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly AbpExceptionHandlingOptions _exceptionHandlingOptions;
public AbpExceptionPageFilter( public AbpExceptionPageFilter(
IExceptionToErrorInfoConverter errorInfoConverter, IExceptionToErrorInfoConverter errorInfoConverter,
IHttpExceptionStatusCodeFinder statusCodeFinder, IHttpExceptionStatusCodeFinder statusCodeFinder,
IJsonSerializer jsonSerializer) IJsonSerializer jsonSerializer,
IOptions<AbpExceptionHandlingOptions> exceptionHandlingOptions)
{ {
_errorInfoConverter = errorInfoConverter; _errorInfoConverter = errorInfoConverter;
_statusCodeFinder = statusCodeFinder; _statusCodeFinder = statusCodeFinder;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_exceptionHandlingOptions = exceptionHandlingOptions.Value;
Logger = NullLogger<AbpExceptionPageFilter>.Instance; Logger = NullLogger<AbpExceptionPageFilter>.Instance;
} }
@ -88,7 +92,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true");
context.HttpContext.Response.StatusCode = (int)_statusCodeFinder.GetStatusCode(context.HttpContext, context.Exception); context.HttpContext.Response.StatusCode = (int)_statusCodeFinder.GetStatusCode(context.HttpContext, context.Exception);
var remoteServiceErrorInfo = _errorInfoConverter.Convert(context.Exception); var remoteServiceErrorInfo = _errorInfoConverter.Convert(context.Exception, _exceptionHandlingOptions.SendExceptionsDetailsToClients);
context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo)); context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo));

4
framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj

@ -19,11 +19,9 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Abp.Auditing\Volo.Abp.Auditing.csproj" /> <ProjectReference Include="..\Volo.Abp.Auditing\Volo.Abp.Auditing.csproj" />
<ProjectReference Include="..\Volo.Abp.Authorization\Volo.Abp.Authorization.csproj" /> <ProjectReference Include="..\Volo.Abp.Authorization\Volo.Abp.Authorization.csproj" />
<ProjectReference Include="..\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" /> <ProjectReference Include="..\Volo.Abp.ExceptionHandling\Volo.Abp.ExceptionHandling.csproj" />
<ProjectReference Include="..\Volo.Abp.Http\Volo.Abp.Http.csproj" /> <ProjectReference Include="..\Volo.Abp.Http\Volo.Abp.Http.csproj" />
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" /> <ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
<ProjectReference Include="..\Volo.Abp.UI\Volo.Abp.UI.csproj" />
<ProjectReference Include="..\Volo.Abp.Uow\Volo.Abp.Uow.csproj" /> <ProjectReference Include="..\Volo.Abp.Uow\Volo.Abp.Uow.csproj" />
<ProjectReference Include="..\Volo.Abp.Validation\Volo.Abp.Validation.csproj" /> <ProjectReference Include="..\Volo.Abp.Validation\Volo.Abp.Validation.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" /> <ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />

12
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs

@ -6,12 +6,10 @@ using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Auditing; using Volo.Abp.AspNetCore.Auditing;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Authorization; using Volo.Abp.Authorization;
using Volo.Abp.Domain; using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http; using Volo.Abp.Http;
using Volo.Abp.Localization;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Security; using Volo.Abp.Security;
using Volo.Abp.UI;
using Volo.Abp.Uow; using Volo.Abp.Uow;
using Volo.Abp.Validation; using Volo.Abp.Validation;
using Volo.Abp.VirtualFileSystem; using Volo.Abp.VirtualFileSystem;
@ -19,16 +17,14 @@ using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore namespace Volo.Abp.AspNetCore
{ {
[DependsOn( [DependsOn(
typeof(AbpAuditingModule), typeof(AbpAuditingModule),
typeof(AbpSecurityModule), typeof(AbpSecurityModule),
typeof(AbpVirtualFileSystemModule), typeof(AbpVirtualFileSystemModule),
typeof(AbpUnitOfWorkModule), typeof(AbpUnitOfWorkModule),
typeof(AbpHttpModule), typeof(AbpHttpModule),
typeof(AbpAuthorizationModule), typeof(AbpAuthorizationModule),
typeof(AbpDddDomainModule), //TODO: Can we remove this? typeof(AbpValidationModule),
typeof(AbpLocalizationModule), typeof(AbpExceptionHandlingModule)
typeof(AbpUiModule), //TODO: Can we remove this?
typeof(AbpValidationModule)
)] )]
public class AbpAspNetCoreModule : AbpModule public class AbpAspNetCoreModule : AbpModule
{ {

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

Loading…
Cancel
Save