diff --git a/docs/en/Modules/Cms-Kit/Index.md b/docs/en/Modules/Cms-Kit/Index.md index fbdc37ec18..eb08a0c53c 100644 --- a/docs/en/Modules/Cms-Kit/Index.md +++ b/docs/en/Modules/Cms-Kit/Index.md @@ -12,6 +12,7 @@ The following features are currently available: * Provides a [**comment**](Comments.md) system to add comments feature to any kind of resource, like blog post or a product review page. * Provides a [**reaction**](Reactions.md) system to add reactions (smileys) feature to any kind of resource, like a blog post or a comment. * Provides a [**rating**](Ratings.md) system to add rating feature to any kind of resource. +* Provides a [**menu**](Menus.md) system to manage public menus dynamically. Click to a feature to understand and learn how to use it. diff --git a/docs/en/Modules/Cms-Kit/Menus.md b/docs/en/Modules/Cms-Kit/Menus.md new file mode 100644 index 0000000000..87486326da --- /dev/null +++ b/docs/en/Modules/Cms-Kit/Menus.md @@ -0,0 +1,88 @@ +# CMS Kit: Pages + +CMS Kit Menu system allows to manage public menus dynamically. + +## The User Interface + +### Menu items + +CMS Kit module admin side adds the following items to the main menu, under the *CMS* menu item: + +* **Menus**: Menu management page. + +`CmsKitAdminMenus` class has the constants for the menu item names. + +### Menus + +#### Menu Management + +Menus page is used to manage dynamic public menus in the system. + +![cms-kit-menus-page](../../images/cmskit-module-menus-page.png) + + + +Created menus will be visible on public site. + +![cms-kit-public-menus](../../images//cmskit-module-menus-public.png) + +# Internals + +## Domain Layer + +#### Aggregates + +This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. + +##### Menus + +- `MenuItem` (aggregate root): A Menu Item presents a single node at menu tree. + +#### Repositories + +This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. + +Following custom repositories are defined for this feature: + +- `IMenuItemRepository` + +#### Domain services + +This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. + +##### Menu Item Manager + +`MenuItemManager` is used to perform some operations for the `MenuItemManager` aggregate root. + +### Application layer + +#### Application services + +- `MenuItemAdminAppService` (implements `IMenuItemAdminAppService`): Implements the management operations of menu system. +- `MenuItemPublicAppService` (implements `IMenuItemPublicAppService`): Implements the public use cases of menu system. + +### Database providers + +#### Common + +##### Table / collection prefix & schema + +All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). + +##### Connection string + +This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. + +See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. + +#### Entity Framework Core + +##### Tables + +- CmsMenuItems + +#### MongoDB + +##### Collections + +- CmsMenuItems \ No newline at end of file diff --git a/docs/en/UI/Angular/Basic-Theme.md b/docs/en/UI/Angular/Basic-Theme.md new file mode 100644 index 0000000000..7b6c98fdcc --- /dev/null +++ b/docs/en/UI/Angular/Basic-Theme.md @@ -0,0 +1,107 @@ +# Angular UI: Basic Theme + +The Basic Theme is a theme implementation for the Angular UI. It is a minimalist theme that doesn't add any styling on top of the plain [Bootstrap](https://getbootstrap.com/). You can take the Basic Theme as the **base theme** and build your own theme or styling on top of it. See the *Customization* section. + +> If you are looking for a professional, enterprise ready theme, you can check the [Lepton Theme](https://commercial.abp.io/themes), which is a part of the [ABP Commercial](https://commercial.abp.io/). + +> See the [Theming document](Theming.md) to learn about themes. + +## Installation + +**This theme is already installed** when you create a new solution using the [startup templates](../../Startup-Templates/Index.md). If you need to manually install it, follow the steps below: + +* Install the [@abp/ng.theme.basic](https://www.npmjs.com/package/@abp/ng.theme.basic) NPM package to your Angular project. +* Open the `src/app/app.module.ts` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule.forRoot()` to the `imports` array. +* Open the `src/app/shared/shared.module` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` and `exports` array. + +The `ThemeBasicModule` is registered own layouts (`ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`) to a service which is exposed by `@abp/ng.core` package on application initialization. + +## Application Layout + +![basic-theme-application-layout](../../images/basic-theme-application-layout.png) + +Application Layout implements the following parts, in addition to the common parts mentioned above; + +* Logo area +* Routes area +* Language selection & user menu +* [Page Alerts](Page-Alerts.md) + +See Application Layout components: + +![application layout components](./images/layout-components.png) + +### How to Use a Layout + +Routes should be added to the menu by calling `add` method `RoutesService`. A layout can be set in the object of your route. See the [modifying the menu](Modifying-the-Menu#how-to-add-a-navigation-element) for more information. + +## Customization + +You have two options two customize this theme: + +### Overriding Styles / Components + +In this approach, you continue to use the theme as an NPM package and customize the parts you need to. There are several ways to customize it; + +#### Override the Styles + +You can simply override the styles in the global styles (`src/styles.scss`) file of your application. + +#### Override the Components + +See the [Component Replacement](Component-Replacement.md) to learn how you can replace components, customize and extend the user interface. + +### Copy & Customize + +You can run the following [ABP CLI](../../CLI.md) command in **Angular** project directory to copy the source code to your solution: + +`abp add-package @abp/ng.theme.basic --with-source-code` + +---- + +Or, you can download the [source code](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-basic) of the Basic Theme, manually copy the project content into your project (`projects/theme-basic` folder), open `angular.json` file and add configuration below to the `projects` object: + +```json +{ + "projects": { + ... + "theme-basic": { + "projectType": "library", + "root": "projects/theme-basic", + "sourceRoot": "projects/theme-basic/src", + "prefix": "abp", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/theme-basic/tsconfig.lib.json", + "project": "projects/theme-basic/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "projects/theme-basic/tsconfig.lib.prod.json" + } + } + } + } + } + } +} +``` + +Then, open the `tsconfig.json` file and add new paths as follows: + +```json +"paths": { + ... + "@abp/ng.theme.basic": ["projects/theme-basic/src/public-api.ts"], + "@abp/ng.theme.basic/testing": ["projects/theme-basic/testing/src/public-api.ts"] +} +``` + + +You can now freely customize the theme based on your application requirements. + +## See Also + +* [Theming](Theming.md) diff --git a/docs/en/UI/Angular/Theming.md b/docs/en/UI/Angular/Theming.md new file mode 100644 index 0000000000..21e28953f8 --- /dev/null +++ b/docs/en/UI/Angular/Theming.md @@ -0,0 +1,228 @@ +# Angular UI: Theming + +## Introduction + +ABP Framework provides a complete **UI Theming** system with the following goals: + +* Reusable [application modules](../../Modules/Index.md) are developed **theme-independent**, so they can work with any UI theme. +* UI theme is **decided by the final application**. +* The theme is distributed via an NPM package, so it is **easily upgradable**. +* The final application can **customize** the selected theme. + +In order to accomplish these goals, ABP Framework; + +* Determines a set of **base libraries** used and adapted by all the themes. So, module and application developers can depend on and use these libraries without depending on a particular theme. +* Provides a system that consists of layout parts (like navigation menus and toolbars) that is implemented by all the themes. So, the modules and the application contribute to the layout to compose a consistent application UI. + +### Current Themes + +Currently, two themes are **officially provided**: + +* The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. +* The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license. + +## Overall + +### The Base Libraries + +All the themes must depend on the [@abp/ng.theme.shared](https://www.npmjs.com/package/@abp/ng.theme.shared) NuGet package, so they are indirectly depending on the following libraries: + +* [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. +* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. +* [NG Bootstrap](https://ng-bootstrap.github.io/#/home) as a component library that supports the Bootstrap and adds extra components like modal and datepicker. +* [Ngx-Datatable](https://swimlane.gitbook.io/ngx-datatable/) as a datatable library. +* [ngx-validate](https://github.com/ng-turkey/ngx-validate) a dynamic validation of reactive forms library. +* [Chart.js](https://www.chartjs.org/) as a widget library. + +These libraries are selected as the base libraries and available to the applications and modules. + +> Bootstrap's JavaScript part is not used since the NG Bootstrap library already provides the necessary functionalities to the Bootstrap components in a native way. + +### The Layout + +All themes must define a layout for the application. The following image shows the user management page in the [Basic Theme](Basic-Theme.md) application layout: + +![basic-theme-application-layout](../../images/basic-theme-application-layout.png) + +And the same page is shown below with the [Lepton Theme](https://commercial.abp.io/themes) application layout: + +![lepton-theme-application-layout](../../images/lepton-theme-application-layout.png) + +As you can see, the page is the same, but the look is completely different in the themes above. + +The application layout typically includes the following parts; + +* Main menu +* Nav items area with the following components; + * User menu + * Language switch dropdown +* [Page alerts](Page-Alerts.md) +* The page content (aka ``) + +## Implementing a Theme + +A theme is simply an NPM package and comes with startup templates. + +### The Easy Way + +The easiest way to create a new theme is to add Basic Theme Source Code to your project via [ABP CLI](../../CLI.md) command and customize it. + +You can run the following command in **Angular** project directory to copy the source code to your solution: + +`abp add-package @abp/ng.theme.basic --with-source-code` + +### Global/Component Styles + +Angular can bundle global style files and component styles with components. +See the [component styles](https://angular.io/guide/component-styles) guide on Angular documentation for more information. + +### Layout Parts + +A typical layout consists of several parts. The theme should include the necessary parts in each layout. + +**Example: The Basic Theme has the following parts for the Application Layout** + +![basic-theme-application-layout-parts](images/basic-theme-application-layout-parts.png) + +The application code and the modules can only show contents in the Page Content part. If they need to change the other parts (to add a menu item, to add a nav item, to change the application name in the logo area...) they should use the ABP Framework APIs. + +The following sections explain the fundamental parts pre-defined by the ABP Framework and can be implemented by the themes. + +> It is a good practice to split the layout into components/partials, so the final application can override them partially for customization purpose. + +#### Logo + +The `application` object of an environment file should be configured to get the name and the logo URL of the application to render in the logo part. Additionally, `LogoComponent` can be replaced. See [Component Replacement](Component-Replacement.md) document for more. + +The [Application Startup Template](../../Startup-Templates/Application.md) has an implementation of this interface to set the values by the application developer. + +#### Main Menu / Routes + +`RoutesService` service is used to manage the main menu items and render them on the layout. + +**Example: Adding a route to the main menu** + +```ts +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { Component } from '@angular/core'; + +@Component(/* component metadata */) +export class AppComponent { + constructor(routes: RoutesService) { + routes.add([ + { + path: '/your-path', + name: 'Your navigation', + order: 101, + iconClass: 'fas fa-question-circle', + requiredPolicy: 'permission key here', + layout: eLayoutType.application, + }, + { + path: '/your-path/child', + name: 'Your child navigation', + parentName: 'Your navigation', + order: 1, + requiredPolicy: 'permission key here', + }, + ]); + } +} +``` + +See the [Modifying the Menu](Modifying-the-Menu.md) document to learn more about the navigation system. + +#### Toolbar / Nav Items + +`NavItemsService` service is used to get the menu's right part items and render on the layout. You can add an HTML content or Angular component as an element to render. + +**Example: Adding an element to right part of the menu** + +````ts +import { NavItemsService } from '@abp/ng.theme.shared'; +import { Component } from '@angular/core'; + +@Component({ + template: ` + + `, +}) +export class MySearchInputComponent {} + + +@Component(/* component metadata */) +export class AppComponent { + constructor(private navItems: NavItemsService) { + navItems.addItems([ + { + id: 'MySearchInput', + order: 1, + component: MySearchInputComponent, + }, + { + id: 'SignOutIcon', + html: '', + action: () => console.log('Clicked the sign out icon'), + order: 101, // puts as last element + }, + ]); + } +} +```` + +> See the [How to Add an Element to Right Part of the Menu](Modifying-the-Menu#how-to-add-an-element-to-right-part-of-the-menu) document to learn more on the nav items system. + +The theme has a responsibility to add two pre-defined items to the toolbar: Language Selection and User Menu. + +##### Language Selection + +Language Selection toolbar item is generally a dropdown that is used to switch between languages. `ConfigStateService` is used to get the list of available languages and `SessionStateService` is used to learn the current language. + +`SessionStateService` is used to get and set the current language. + +**Example: Get the currently selected language** + +````ts +import {SessionStateService} from '@abp/ng.core'; + +//... + +constructor(private sessionState: SessionStateService) { + const lang = this.sessionState.getLanguage() +} +```` + +**Example: Set the selected language** + +````ts +import {SessionStateService} from '@abp/ng.core'; + +//... + +constructor(private sessionState: SessionStateService) { + const lang = this.sessionState.setLanguage('en') +} +```` + +##### User Menu + +User menu is a component that can be replaceable. See an example to learn how can you replace it: + +````ts +import { eThemeBasicComponents } from '@abp/ng.theme.basic'; +import { NavItemsService } from '@abp/ng.theme.shared'; +import { Component } from '@angular/core'; + +@Component({/* component metadata */}) +export class AppComponent { + constructor(private navItems: NavItemsService) { + this.navItems.patchItem(eThemeBasicComponents.CurrentUser, { component: MyUserMenuComponent }); + } +} +```` + +[`ConfigStateService`](Config-State-Service.md) service can be used to obtain the `application-configuration` API response (e.g. getting current user or tenant). + +#### Page Alerts + +`PageAlertService` service is used to get the current page alerts to render on the layout. See the [Page Alerts](Page-Alerts.md) document to learn more. \ No newline at end of file diff --git a/docs/en/UI/Angular/images/basic-theme-application-layout-parts.png b/docs/en/UI/Angular/images/basic-theme-application-layout-parts.png new file mode 100644 index 0000000000..934b0e0528 Binary files /dev/null and b/docs/en/UI/Angular/images/basic-theme-application-layout-parts.png differ diff --git a/docs/en/UI/Blazor/Basic-Theme.md b/docs/en/UI/Blazor/Basic-Theme.md index 6a71e50c84..2c034f258a 100644 --- a/docs/en/UI/Blazor/Basic-Theme.md +++ b/docs/en/UI/Blazor/Basic-Theme.md @@ -38,7 +38,7 @@ You have two options two customize this theme: ### Overriding Styles / Components -In this approach, you continue to use the the theme as NuGet and NPM packages and customize the parts you need to. There are several ways to customize it; +In this approach, you continue to use the theme as NuGet and NPM packages and customize the parts you need to. There are several ways to customize it; #### Override the Styles diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index c946b8401c..5a2b18d365 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -365,8 +365,8 @@ "path": "Object-To-Object-Mapping.md" }, { - "text":"String Encryption", - "path":"String-Encryption.md" + "text": "String Encryption", + "path": "String-Encryption.md" }, { "text": "Text Templating", @@ -1025,6 +1025,19 @@ "text": "Customization Guide", "path": "UI/Angular/Customization-User-Interface.md" }, + { + "text": "Theming", + "items": [ + { + "text": "Overall", + "path": "UI/Angular/Theming.md" + }, + { + "text": "The Basic Theme", + "path": "UI/Angular/Basic-Theme.md" + } + ] + }, { "text": "Modifying the Menu", "path": "UI/Angular/Modifying-the-Menu.md" diff --git a/docs/en/images/cmskit-module-menus-page.png b/docs/en/images/cmskit-module-menus-page.png new file mode 100644 index 0000000000..c265763788 Binary files /dev/null and b/docs/en/images/cmskit-module-menus-page.png differ diff --git a/docs/en/images/cmskit-module-menus-public.png b/docs/en/images/cmskit-module-menus-public.png new file mode 100644 index 0000000000..0b67064334 Binary files /dev/null and b/docs/en/images/cmskit-module-menus-public.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 95ac94f7c0..119d083ad1 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -385,6 +385,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MongoDB.Tests.Seco EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.IdentityModel.Tests", "test\Volo.Abp.IdentityModel.Tests\Volo.Abp.IdentityModel.Tests.csproj", "{40C6740E-BFCA-4D37-8344-3D84E2044BB2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Threading.Tests", "test\Volo.Abp.Threading.Tests\Volo.Abp.Threading.Tests.csproj", "{7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1147,6 +1149,10 @@ Global {40C6740E-BFCA-4D37-8344-3D84E2044BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU {40C6740E-BFCA-4D37-8344-3D84E2044BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU {40C6740E-BFCA-4D37-8344-3D84E2044BB2}.Release|Any CPU.Build.0 = Release|Any CPU + {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1341,6 +1347,7 @@ Global {75D8DADB-3FA9-4C1D-B23A-DBFD08133B7C} = {447C8A77-E5F0-4538-8687-7383196D04EA} {90B1866A-EF99-40B9-970E-B898E5AA523F} = {447C8A77-E5F0-4538-8687-7383196D04EA} {40C6740E-BFCA-4D37-8344-3D84E2044BB2} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs index f5541e3b40..f3d21a804b 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs @@ -64,6 +64,7 @@ namespace Volo.Abp.AspNetCore.MultiTenancy context, requestCulture ); + context.Items[AbpRequestLocalizationMiddleware.HttpContextItemName] = true; } await next(context); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs index b0fa63e4f5..f5324656b9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs @@ -33,6 +33,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Localization new RequestCulture(culture, uiCulture) ); + HttpContext.Items[AbpRequestLocalizationMiddleware.HttpContextItemName] = true; + var context = new QueryStringCultureReplacementContext(HttpContext, new RequestCulture(culture, uiCulture), returnUrl); await QueryStringCultureReplacement.ReplaceAsync(context); diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs index 06f3922b22..dad2b240d8 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs @@ -10,6 +10,8 @@ namespace Microsoft.AspNetCore.RequestLocalization { public class AbpRequestLocalizationMiddleware : IMiddleware, ITransientDependency { + public const string HttpContextItemName = "__AbpSetCultureCookie"; + private readonly IAbpRequestLocalizationOptionsProvider _requestLocalizationOptionsProvider; private readonly ILoggerFactory _loggerFactory; @@ -29,6 +31,23 @@ namespace Microsoft.AspNetCore.RequestLocalization _loggerFactory ); + context.Response.OnStarting(() => + { + if (context.Items[HttpContextItemName] == null) + { + var requestCultureFeature = context.Features.Get(); + if (requestCultureFeature?.Provider is QueryStringRequestCultureProvider) + { + AbpRequestCultureCookieHelper.SetCultureCookie( + context, + requestCultureFeature.RequestCulture + ); + } + } + + return Task.CompletedTask; + }); + await middleware.Invoke(context); } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs index c7c961176c..ce722b1176 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs @@ -6,13 +6,26 @@ using Volo.Abp.Threading; namespace Volo.Abp.AspNetCore.Threading { [Dependency(ReplaceServices = true)] - public class HttpContextCancellationTokenProvider : ICancellationTokenProvider, ITransientDependency + public class HttpContextCancellationTokenProvider : CancellationTokenProviderBase, ITransientDependency { - public CancellationToken Token => _httpContextAccessor.HttpContext?.RequestAborted ?? CancellationToken.None; - private readonly IHttpContextAccessor _httpContextAccessor; - public HttpContextCancellationTokenProvider(IHttpContextAccessor httpContextAccessor) + public override CancellationToken Token + { + get + { + if (OverrideValue != null) + { + return OverrideValue.CancellationToken; + } + return _httpContextAccessor.HttpContext?.RequestAborted ?? CancellationToken.None; + } + } + + public HttpContextCancellationTokenProvider( + IAmbientScopeProvider cancellationTokenOverrideScopeProvider, + IHttpContextAccessor httpContextAccessor) + : base(cancellationTokenOverrideScopeProvider) { _httpContextAccessor = httpContextAccessor; } diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj index f8c8658e5f..dcfe16cce3 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -14,10 +14,10 @@ - - - - + + + + diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenOverride.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenOverride.cs new file mode 100644 index 0000000000..371e3d6652 --- /dev/null +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenOverride.cs @@ -0,0 +1,14 @@ +using System.Threading; + +namespace Volo.Abp.Threading +{ + public class CancellationTokenOverride + { + public CancellationToken CancellationToken { get; } + + public CancellationTokenOverride(CancellationToken cancellationToken) + { + CancellationToken = cancellationToken; + } + } +} diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenProviderBase.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenProviderBase.cs new file mode 100644 index 0000000000..20101a95da --- /dev/null +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenProviderBase.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; + +namespace Volo.Abp.Threading +{ + public abstract class CancellationTokenProviderBase : ICancellationTokenProvider + { + public const string CancellationTokenOverrideContextKey = "Volo.Abp.Threading.CancellationToken.Override"; + + public abstract CancellationToken Token { get; } + + protected IAmbientScopeProvider CancellationTokenOverrideScopeProvider { get; } + + protected CancellationTokenOverride OverrideValue => CancellationTokenOverrideScopeProvider.GetValue(CancellationTokenOverrideContextKey); + + protected CancellationTokenProviderBase(IAmbientScopeProvider cancellationTokenOverrideScopeProvider) + { + CancellationTokenOverrideScopeProvider = cancellationTokenOverrideScopeProvider; + } + + public IDisposable Use(CancellationToken cancellationToken) + { + return CancellationTokenOverrideScopeProvider.BeginScope(CancellationTokenOverrideContextKey, new CancellationTokenOverride(cancellationToken)); + } + } +} diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/ICancellationTokenProvider.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/ICancellationTokenProvider.cs index 24e303adde..06dc008d52 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/ICancellationTokenProvider.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/ICancellationTokenProvider.cs @@ -1,9 +1,12 @@ -using System.Threading; +using System; +using System.Threading; namespace Volo.Abp.Threading { public interface ICancellationTokenProvider { CancellationToken Token { get; } + + IDisposable Use(CancellationToken cancellationToken); } } diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs index ab6320546c..6c2b4d364c 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs @@ -2,15 +2,15 @@ namespace Volo.Abp.Threading { - public class NullCancellationTokenProvider : ICancellationTokenProvider + public class NullCancellationTokenProvider : CancellationTokenProviderBase { public static NullCancellationTokenProvider Instance { get; } = new NullCancellationTokenProvider(); - public CancellationToken Token { get; } = CancellationToken.None; + public override CancellationToken Token => OverrideValue?.CancellationToken ?? CancellationToken.None; private NullCancellationTokenProvider() + : base(new AmbientDataContextAmbientScopeProvider(new AsyncLocalAmbientDataContext())) { - } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Threading.Tests/Volo.Abp.Threading.Tests.csproj b/framework/test/Volo.Abp.Threading.Tests/Volo.Abp.Threading.Tests.csproj new file mode 100644 index 0000000000..cbca0b1329 --- /dev/null +++ b/framework/test/Volo.Abp.Threading.Tests/Volo.Abp.Threading.Tests.csproj @@ -0,0 +1,16 @@ + + + + + + net5.0 + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Threading.Tests/Volo/Abp/Threading/AbpThreadingTestModule.cs b/framework/test/Volo.Abp.Threading.Tests/Volo/Abp/Threading/AbpThreadingTestModule.cs new file mode 100644 index 0000000000..de530512d3 --- /dev/null +++ b/framework/test/Volo.Abp.Threading.Tests/Volo/Abp/Threading/AbpThreadingTestModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.Threading +{ + [DependsOn( + typeof(AbpThreadingModule), + typeof(AbpTestBaseModule) + )] + public class AbpThreadingTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + } + } +} diff --git a/framework/test/Volo.Abp.Threading.Tests/Volo/Abp/Threading/NullCancellationTokenProvider_Tests.cs b/framework/test/Volo.Abp.Threading.Tests/Volo/Abp/Threading/NullCancellationTokenProvider_Tests.cs new file mode 100644 index 0000000000..34c5988eb1 --- /dev/null +++ b/framework/test/Volo.Abp.Threading.Tests/Volo/Abp/Threading/NullCancellationTokenProvider_Tests.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Threading +{ + public class NullCancellationTokenProvider_Tests : AbpIntegratedTest + { + private readonly ICancellationTokenProvider _cancellationTokenProvider; + + public NullCancellationTokenProvider_Tests() + { + _cancellationTokenProvider = NullCancellationTokenProvider.Instance; + } + + [Fact] + public void Should_Return_None_Token() + { + _cancellationTokenProvider.Token.ShouldBe(CancellationToken.None); + } + + [Fact] + public void Should_Return_Specific_Token() + { + var cts = new CancellationTokenSource(); + + using (_cancellationTokenProvider.Use(cts.Token)) + { + var newCancellationTokenProvider = NullCancellationTokenProvider.Instance; + + newCancellationTokenProvider.Token.ShouldBe(cts.Token); + } + + _cancellationTokenProvider.Token.ShouldBe(CancellationToken.None); + } + + [Fact] + public void Should_Cancel_After_100_Milliseconds() + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMilliseconds(100)); + + using (_cancellationTokenProvider.Use(cts.Token)) + { + var newCancellationTokenProvider = NullCancellationTokenProvider.Instance; + Should.Throw(() => LongTask(1000, newCancellationTokenProvider.Token)); + } + } + + private void LongTask(int loopCounter, CancellationToken cancellationToken = default) + { + for (var i = 0; i < loopCounter; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + Thread.Sleep(10); + } + } + } +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj index 1c88aabcd5..aa0170cca4 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index 362a1068f2..b83e86df6f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj index 74e8f8ca07..0bde45f899 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/MyCompanyName.MyProjectName.Blazor.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/MyCompanyName.MyProjectName.Blazor.Host.csproj index 9beaac9818..6adc30e7f6 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/MyCompanyName.MyProjectName.Blazor.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/MyCompanyName.MyProjectName.Blazor.Host.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj index 923814f92b..098b542d4f 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs index 0829a70a7e..beab7c5132 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs @@ -119,7 +119,7 @@ namespace MyCompanyName.MyProjectName options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文")); options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "Deutsch")); - options.Languages.Add(new LanguageInfo("es", "es", "Español"s)); + options.Languages.Add(new LanguageInfo("es", "es", "Español")); }); Configure(options =>