diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 062bf71d89..6ba3ad7a6d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-dotnet@master with: - dotnet-version: 5.0.100-rc.2.20479.15 + dotnet-version: 5.0.100 - name: Build All run: .\build-all.ps1 diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 2a57c6b25d..e029499b06 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -30,6 +30,8 @@ "UserNameNotFound": "There is no user with username {0}", "SuccessfullyAddedToNewsletter": "Thanks you for subscribing to our newsletter!", "MyProfile": "My profile", - "EmailNotValid": "Please enter a valid email address." + "EmailNotValid": "Please enter a valid email address.", + "JoinOurMarketingNewsletter": "Join our marketing newsletter", + "WouldLikeToReceiveMarketingMaterials": "I would like to receive marketing materials like product deals & special offers." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json index 2d146db401..0c7a94424f 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json @@ -90,6 +90,15 @@ "LatestArticles": "Latest Articles", "ArticleRequests": "Article Requests", "AllArticleRequests": "See All Article Requests", - "ArticleAssignRequest": "Want to write this article? Click here to assign yourself." + "ArticleAssignRequest": "assign yourself.", + "SubscribeToTheNewsletter": "Subscribe to the Newsletter", + "NewsletterEmailDefinition": "Get information about happenings in ABP like new releases, free sources, articles, and more.", + "NoThanks": "No, thanks", + "MaybeLater": "Maybe later", + "JoinOurArticleNewsletter": "Join our article newsletter", + "Community": "Community", + "Marketing": "Marketing", + "ArticleAssignMessage": "Want to write this article? Click here to", + "CommunityPrivacyPolicyConfirmation": "I agree to the Terms & Conditions and Privacy Policy." } } diff --git a/common.DotSettings b/common.DotSettings index 6f40d029a7..7edbd05e75 100644 --- a/common.DotSettings +++ b/common.DotSettings @@ -20,6 +20,17 @@ False False SQL + False + Never + Never + False + Never + Never + Never + Never + Never + True + True False False False diff --git a/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/POST.md b/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/POST.md new file mode 100644 index 0000000000..d8e7c0389f --- /dev/null +++ b/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/POST.md @@ -0,0 +1,142 @@ +# ABP Framework 4.0 RC Has Been Published based on .NET 5.0! + +Today, we have released the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/)) `4.0.0-rc.1` that is based on the **.NET 5.0**. This blog post introduces the new features and important changes in the new version. + +> **The planned release date for the [4.0.0 final](https://github.com/abpframework/abp/milestone/45) version is November 26, 2020**. + +## Get Started with the 4.0 RC.1 + +If you want to try the version `4.0.0-rc.1` today, follow the steps below; + +1) **Upgrade** the ABP CLI to the version `4.0.0-rc.1` using a command line terminal: + +````bash +dotnet tool update Volo.Abp.Cli -g --version 4.0.0-rc.1 +```` + +**or install** if you haven't installed before: + +````bash +dotnet tool install Volo.Abp.Cli -g --version 4.0.0-rc.1 +```` + +2) Create a **new application** with the `--preview` option: + +````bash +abp new BookStore --preview +```` + +See the [ABP CLI documentation](https://docs.abp.io/en/abp/3.3/CLI) for all the available options. + +> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. + +## Migrating From 3.x to 4.0 + +The version 4.0 comes with some major changes including the **migration from .NET Core 3.1 to .NET 5.0**. + +We've prepared a **detailed [migration document](https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0)** to explain all the changes and the actions you need to take while upgrading your existing solutions. + +## What's new with the ABP Framework 4.0 + +### The Blazor UI + +The Blazor UI is now stable and officially supported. The [web application development tutorial](https://docs.abp.io/en/abp/4.0/Tutorials/Part-1?UI=Blazor) has been updated based on the version 4.0. + +#### abp bundle command + +Introducing the `abp bundle` CLI command to manage static JavaScript & CSS file dependencies of a Blazor application. This command is currently used to add the dependencies to the `index.html` file in the dependency order by respecting to modularity. In the next version it will automatically unify & minify the files. The documentation is being prepared. + +#### Removed the JQuery & Bootstrap JavaScript + +Removed JQuery & Bootstrap JavaScript dependencies for the Blazor UI. + +>There are some other changes in the startup template and some public APIs. Follow the [Migration Guide](https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0) to apply changes for existing solutions that you're upgrading from the version 3.3. While we will continue to make improvements add new features, we no longer make breaking changes on the existing APIs until the version 5.0. + +#### Others + +A lot of minor and major improvements have been done for the Blazor UI. Some of them are listed below: + +* Implemented `IComponentActivator` to resolve the component from the `IServiceProvider`. So, you can now inject dependencies into the constructor of your razor component. +* Introduced the `AbpComponentBase` base class that you derive your components from. It has useful base properties that you can use in your pages/components. +* Introduced `IUiNotificationService` service to show toast notifications on the UI. +* Improved the `IUiMessageService` to show message & confirmation dialogs. + +### System.Text.Json + +ABP Framework 4.0 uses the System.Text.Json by default as the JSON serialization library. It, actually, using a hybrid approach: Continues to use the Newtonsoft.Json when it needs to use the features not supported by the System.Text.Json. + +Follow the [Migration Guide](https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0) to learn how to configure to use the Newtonsoft.Json for some specific types or switch back to the Newtonsoft.Json as the default JSON serializer. + +### Identity Server 4 Upgrade + +ABP Framework upgrades the [IdentityServer4](https://www.nuget.org/packages/IdentityServer4) library from 3.x to 4.1.1 with the ABP Framework version 4.0. IdentityServer 4.x has a lot of changes. Some of them are **breaking changes in the data structure**. + +Follow the [Migration Guide](https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0) to upgrade existing solutions. + +### Creating a New Module Inside the Application + +ABP CLI has now a command to create a new module and add it to an existing solution. In this way, you can create modular applications easier than before. + +Example: Create a *ProductManagement* module into your solution. + +````bash +abp add-module ProductManagement --new --add-to-solution-file +```` + +Execute this command in a terminal in the root folder of your solution. If you don't specify the `--add-to-solution-file` option, then the module projects will not be added to the main solution, but the project references still be added. In this case, you need to open the module's solution to develop the module. + +See the [CLI document](https://docs.abp.io/en/abp/4.0/CLI) for other options. + +### WPF Startup Template + +Introducing the WPF startup template for the ABP Framework. Use the ABP CLI new command to create a new WPF application: + +````bash +abp new MyWpfApp -t wpf +```` + +This is a minimalist, empty project template that is integrated to the ABP Framework. + +### New Languages + +**Thanks to the contributors** from the ABP Community, the framework modules and the startup template have been localized to **German** (by [Alexander Pilhar](https://github.com/alexanderpilhar) & [Nico Lachmuth](https://github.com/tntwist)) and **Spanish** (by [Jose Manuel Gonzalez](https://github.com/jmglezgz)) languages. + +### Other Notes + +* Since [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) library not supports transactions, you can use transactions in unit tests for MongoDB. + +## What's new with the ABP Commercial 4.0 + +### The Blazor UI + +The Blazor UI for the ABP Commercial is also becomes stable and feature rich with the version 4.0; + +* [ABP Suite](https://commercial.abp.io/tools/suite) now supports to generate CRUD pages for the Blazor UI. +* Completed the [Lepton Theme](https://commercial.abp.io/themes) for the Blazor UI. +* Implemented the [File Management](https://commercial.abp.io/modules/Volo.FileManagement) module for the Blazor UI. + +### The ABP Suite + +While creating create/edit modals with a navigation property, we had two options: A dropdown to select the target entity and a modal to select the entity by searching with a data table. + +Dropdown option now supports **lazy load, search and auto-complete**. In this way, selecting a navigation property becomes much easier and supports large data sets on the dropdown. + +**Example: Select an author while creating a new book** + +![abp-suite-auto-complete-dropdown](abp-suite-auto-complete-dropdown.png) + +With the new version, you can **disable backend code generation** on CRUD page generation. This is especially useful if you want to regenerate the page with a different UI framework, but don't want to regenerate the server side code. + +### Identity Server Management UI Revised + +Completely revised the Identity Server Management UI based on the IDS 4.x changes. + +## About the Next Release + +The next feature version, `4.1.0`, will mostly focus on completing the missing documents, fixing bugs, performance optimizations and improving the Blazor UI features. The planned preview release date for the version `4.1.0` is December 10 and the final (stable) version release date is December 24. + +Follow the [GitHub milestones](https://github.com/abpframework/abp/milestones) for all the planned ABP Framework version release dates. + +## Feedback + +Please check out the ABP Framework 4.0.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. **The planned release date for the [4.0.0 final](https://github.com/abpframework/abp/milestone/45) version is November 26**. diff --git a/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/abp-suite-auto-complete-dropdown.png b/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/abp-suite-auto-complete-dropdown.png new file mode 100644 index 0000000000..2021ef42bd Binary files /dev/null and b/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/abp-suite-auto-complete-dropdown.png differ diff --git a/docs/en/CLI.md b/docs/en/CLI.md index df20031d71..48ec779c1e 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -178,7 +178,7 @@ abp add-module Volo.Blogging * This example adds the `Volo.Blogging` module to the solution. ```bash -abp add-module ProductManagement --new-template --add-to-solution-file +abp add-module ProductManagement --new --add-to-solution-file ``` * This command creates a fresh new module customized for your solution (named `ProductManagement`) and adds it to your solution. @@ -189,8 +189,8 @@ abp add-module ProductManagement --new-template --add-to-solution-file * `--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. * `-sp` or `--startup-project`: Relative path to the project folder of the startup project. Default value is the current folder. -* `--new-template`: Creates a fresh new module (customized for your solution) and adds it to your solution. -* `--with-source-code`: Downloads the source code of the module to your solution folder and uses local project references instead of NuGet/NPM packages. This options is always `True` if `--new-template` is used. +* `--new`: Creates a fresh new module (customized for your solution) and adds it to your solution. +* `--with-source-code`: Downloads the source code of the module to your solution folder and uses local project references instead of NuGet/NPM packages. This options is always `True` if `--new` is used. * `--add-to-solution-file`: Adds the downloaded/created 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 `True`.) ### get-source diff --git a/docs/en/Customizing-Application-Modules-Overriding-Services.md b/docs/en/Customizing-Application-Modules-Overriding-Services.md index 97df12a3a6..133b2e89eb 100644 --- a/docs/en/Customizing-Application-Modules-Overriding-Services.md +++ b/docs/en/Customizing-Application-Modules-Overriding-Services.md @@ -59,7 +59,6 @@ In most cases, you will want to change one or a few methods of the current imple ### Example: Overriding an Application Service ````csharp -//[RemoteService(IsEnabled = false)] // If you use dynamic controller feature you can disable remote service. Prevent creating duplicate controller for the application service. [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(MyIdentityUserAppService))] public class MyIdentityUserAppService : IdentityUserAppService diff --git a/docs/en/Migration-Guides/Abp-4_0-Angular.md b/docs/en/Migration-Guides/Abp-4_0-Angular.md new file mode 100644 index 0000000000..60f6e46de5 --- /dev/null +++ b/docs/en/Migration-Guides/Abp-4_0-Angular.md @@ -0,0 +1,11 @@ +# Angular UI 3.3 to 4.0 Migration Guide + +### Removed the Angular Account Module Public UI + +Angular UI is using the Authorization Code Flow to authenticate since the version 3.1.0 by default. Starting from the version 4.0, this is becoming the only option, because it is the recommended way of authenticating SPAs. + +If you haven't done it yet, see [this post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released) to change the authentication of your application. + +### Removed the SessionState + +Use `SessionStateService` instead of the `SessionState`. See [this issue](https://github.com/abpframework/abp/issues/5606) for details. \ No newline at end of file diff --git a/docs/en/Migration-Guides/Abp-4_0-Blazor.md b/docs/en/Migration-Guides/Abp-4_0-Blazor.md new file mode 100644 index 0000000000..0f01899f7c --- /dev/null +++ b/docs/en/Migration-Guides/Abp-4_0-Blazor.md @@ -0,0 +1,88 @@ +# Blazor UI 3.3 to 4.0 Migration Guide + +## Startup Template Changes + +These changes are required to manually applied in your own solution. It would be easier if you create a new solution based on 4.0 with the same name of your current solution then compare the files. + +### Csproj File / Dependencies + +* Add `true` to the `PropertyGroup` section of your project (`.csproj`) file. +* Update the `Blazorise.*` packages to the latest version (to the latest RC for the ABP 4.0 preview). + +### wwwroot/index.html + +There are some changes made in the index.html file; + +* Removed JQuery & Bootstrap JavaScript dependencies +* Replaced Bootstrap and FontAwesome imports with local files instead of CDN usages. +* Re-arranged some ABP CSS file locations. +* Introduced the `abp bundle` CLI command to manage global Style/Script file imports. + +Follow the steps below to apply the changes; + +1. Add the bundle contributor class into your project (it will be slightly different based on your solution namespaces): + +````csharp +using Volo.Abp.Bundling; + +namespace MyCompanyName.MyProjectName.Blazor +{ + public class MyProjectNameBundleContributer : IBundleContributer + { + public void AddScripts(BundleContext context) + { + } + + public void AddStyles(BundleContext context) + { + context.Add("main.css"); + } + } +} +```` + +If you are using another global style/script files, add them here. + +2. Remove all the `` elements and replace with the following comment tags: + +````html + + +```` + +3. Remove all the `` elements and replace with the following comment tags: + +````html + + +```` + +4. Execute the following command in a terminal in the root folder of the Blazor project (`.csproj`) file (ensure that you're using the ABP CLI version 4.0): + +````bash +abp bundle +```` + +This will fill in the `Styles` and `Scripts` tags based on the dependencies. + +5. You can clean the `blazor-error-ui` related sections from your `main.css` file since they are not needed anymore. + +### The Root Element + +This change is optional but recommended. + +* Change `...` to `
...
` in the `wwwroot/index.html`. +* Change `builder.RootComponents.Add("app");` to `builder.RootComponents.Add("#ApplicationContainer");` in the *YourProjectBlazorModule.cs*. + +## AbpCrudPageBase Changes + +If you've derived your pages from the `AbpCrudPageBase` class, then you may need to apply the following changes; + +- `OpenEditModalAsync` method gets `EntityDto` instead of id (`Guid`) parameter. Pass `context` instead of `context.Id`. +- `DeleteEntityAsync` method doesn't display confirmation dialog anymore. You can use the new `EntityActions` component in Data Grids to show confirmation messages. You can also inject `IUiMessageService` to your page or component and call the `ConfirmAsync` explicitly. +- Added `GetListInput` as a base property that is used to filter while getting the entities from the server. + +## Others + +- Refactored namespaces for some Blazor components ([#6015](https://github.com/abpframework/abp/issues/6015)). +- Removed Async Suffix from IUiMessageService ([#6123](https://github.com/abpframework/abp/pull/6123)). \ No newline at end of file diff --git a/docs/en/Migration-Guides/Abp-4_0-MVC-Razor-Pages.md b/docs/en/Migration-Guides/Abp-4_0-MVC-Razor-Pages.md new file mode 100644 index 0000000000..d2fa97ec5e --- /dev/null +++ b/docs/en/Migration-Guides/Abp-4_0-MVC-Razor-Pages.md @@ -0,0 +1,6 @@ +# MVC / Razor Pages UI 3.3 to 4.0 Migration Guide + +## Use IBrandingProvider in the Volo.Abp.UI Package + +This will be a breaking change for MVC UI, but very easy to fix. `IBrandingProvider` is being moved from `Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components` to `Volo.Abp.Ui.Branding` namespace. So, just update the namespace imports. + diff --git a/docs/en/Migration-Guides/Abp-4_0.md b/docs/en/Migration-Guides/Abp-4_0.md index 4696dddeea..50d2d34bf1 100644 --- a/docs/en/Migration-Guides/Abp-4_0.md +++ b/docs/en/Migration-Guides/Abp-4_0.md @@ -1,53 +1,70 @@ # ABP Framework 3.3 to 4.0 Migration Guide -## Auto API Controller Route Changes +This document introduces the breaking changes done in the ABP Framework 4.0 and explains how to fix your 3.x based solutions while upgrading to the ABP Framework 4.0. -The route calculation for the [Auto API Controllers](https://docs.abp.io/en/abp/latest/API/Auto-API-Controllers) is changing with the ABP Framework version 4.0 ([#5325](https://github.com/abpframework/abp/issues/5325)). Previously, **camelCase** route paths were being used. Beginning from the version 4.0, it uses **kebab-case** route paths where it is possible. +> See this blog post (TODO: LINK) to learn what's new with the ABP Framework 4.0. This document only focuses on the breaking changes. -**A typical auto API before v4.0** +## Overall -![route-before-4](images/route-before-4.png) +Here, the overall list of the changes; -**camelCase route parts become kebab-case with 4.0** +* Upgraded to the .NET 5.0 [(#6118](https://github.com/abpframework/abp/issues/6118)). +* Moved from Newtonsoft.Json to System.Text.Json [(#1198](https://github.com/abpframework/abp/issues/1198)). +* Upgraded to the Identity Server 4.1.1 ([#4461](https://github.com/abpframework/abp/issues/4461)). +* Switched to `kebab-case` for conventional URLs for the auto API controller routes ([#5325](https://github.com/abpframework/abp/issues/5325)). +* Removed Retry for the Dynamic HTTP Client Proxies ([#6090](https://github.com/abpframework/abp/issues/6090)). +* Creation audit properties of the entities made read-only ([#6020](https://github.com/abpframework/abp/issues/6020)). +* Changed type of the IHasExtraProperties.ExtraProperties ([#3751](https://github.com/abpframework/abp/issues/3751)). +* Use IBrandingProvider in the Volo.Abp.UI package and remove the one in the Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared ([#5375](https://github.com/abpframework/abp/issues/5375)). +* Removed the Angular Account Module Public UI (login, register... pages) since they are not being used in the default (authorization code) flow ([#5652](https://github.com/abpframework/abp/issues/5652)). +* Removed the SessionState in the @abp/ng.core package ([#5606](https://github.com/abpframework/abp/issues/5606)). +* Made some API revisions & startup template changes for the Blazor UI. -![route-4](images/route-4.png) +## Upgraded to .NET 5.0 -If it is hard to change it for your application, you can continue to use the version 3.x route strategy, by following one of the approaches; +ABP Framework has been moved to .NET 5.0. So, if you want to upgrade to the ABP Framework 4.0, you also need to upgrade to .NET 5.0. -- Set `UseV3UrlStyle` to `true` in the options of the `options.ConventionalControllers.Create(...)` method. Example: +See the [Migrate from ASP.NET Core 3.1 to 5.0](https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50) document to learn how to upgrade your solution to .NET 5.0. -```csharp -options.ConventionalControllers - .Create(typeof(BookStoreApplicationModule).Assembly, opts => - { - opts.UseV3UrlStyle = true; - }); -``` +## Moved to System.Text.Json -This approach effects only the controllers for the `BookStoreApplicationModule`. +ABP Framework 4.0 uses the System.Text.Json by default as the JSON serialization library. It, actually, using a hybrid approach: Continues to use the Newtonsoft.Json when it needs to use features not supported by the System.Text.Json. -- Set `UseV3UrlStyle` to `true` for the `AbpConventionalControllerOptions` to set it globally. Example: +### Unsupported Types -```csharp -Configure(options => +If you want to use the Newtonsoft.Json to serialize/deserialize for some specific types, you can configure the `AbpSystemTextJsonSerializerOptions` in your module's `ConfigureServices` method. + +**Example: Use Newtonsoft.Json for `MySpecialClass`** + +````csharp +Configure(options => { - options.UseV3UrlStyle = true; + options.UnsupportedTypes.AddIfNotContains(typeof(MySpecialClass)); }); -``` +```` + +### Always Use the Newtonsoft.Json -Setting it globally effects all the modules in a modular application. +If you want to continue to use the Newtonsoft.Json library for all the types, you can set `UseHybridSerializer` to false in the `PreConfigureServices` method of your module class: -## Identity Server Changes +````csharp +PreConfigure(options => +{ + options.UseHybridSerializer = false; +}); +```` + +## Upgraded to Identity Server 4.1.1 -ABP Framework upgrades the [IdentityServer4](https://www.nuget.org/packages/IdentityServer4) library from 3.x to 4.x with the ABP Framework version 4.0. IdentityServer 4.x has a lot of changes, some of them are **breaking changes in the data structure**. +ABP Framework upgrades the [IdentityServer4](https://www.nuget.org/packages/IdentityServer4) library from 3.x to 4.1.1 with the ABP Framework version 4.0. IdentityServer 4.x has a lot of changes. Some of them are **breaking changes in the data structure**. ### Entity Changes -Entity changed don't directly affect your application, however it is good to know. +Entity changes don't directly affect your application; however, it is good to know. #### ApiScope -As the **most important breaking change**, Identity Server 4.x places the `ApiScope` as an independent aggregate root. Previously it was a part of the to `ApiResource` aggregate. This requires manual operation. See the _Database Changes_ section. +As the **most critical breaking change**; Identity Server 4.x defines the `ApiScope` as an independent aggregate root. Previously, it was the child entity of the `ApiResource`. This change requires manual operation. See the _Database Changes_ section. Also, added `Enabled(string)` and `Description(bool,true)` properties. @@ -57,40 +74,40 @@ Also, added `Enabled(string)` and `Description(bool,true)` properties. #### Client -- Added `RequireRequestObject (bool)` and `AllowedIdentityTokenSigningAlgorithms (string)` properties. -- Changed default value of `RequireConsent` from `true` to `false`. -- Changed default value of `RequirePkce` from `false` to `true`. +- Added `RequireRequestObject ` and `AllowedIdentityTokenSigningAlgorithms ` properties. +- Changed the default value of `RequireConsent` from `true` to `false`. +- Changed the default value of `RequirePkce` from `false` to `true`. #### DeviceFlowCodes -- Added `SessionId (string)` and `Description (string)` properties. +- Added `SessionId ` and `Description ` properties. #### PersistedGrant -- Added `SessionId (string)` and `Description(string)` and `ConsumedTime (DateTime?)` properties +- Added `SessionId `, `Description ` and `ConsumedTime ` properties ### Database Changes > Attention: **Please backup your database** before the migration! -**If you are upgrading from 3.x, then there are some change should be done in your database.** +**If you are upgrading from 3.x, then there are some steps should be done in your database.** #### Database Schema Migration -If you are using **Entity Framework Core**, you need to add a new database migration, using the `Add-Migration` command, and apply changes to the database. Please **review the migration** script and read the sections below to understand if it affects your existing data. Otherwise, you may **loose some of your configuration**, which may not be easy to remember and re-configure. +If you are using **Entity Framework Core**, you need to add a new database migration, using the `Add-Migration` command, and apply changes to the database. Please **review the migration** script and read the sections below to understand if it affects your existing data. Otherwise, you may **lose some of your configuration**, which may not be easy to remember and re-configure. #### Seed Code -If you haven't customize the `IdentityServerDataSeedContributor` and haven't customized the initial data inside the `IdentityServer*` tables; +If you haven't customized the `IdentityServerDataSeedContributor` and haven't customized the initial data inside the `IdentityServer*` tables; 1. Update `IdentityServerDataSeedContributor` class by comparing to [the latest code](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs). You probably only need to add the `CreateApiScopesAsync` method and the code related to it. -2. Then you can simply clear all the **table data** in these tables then execute the `DbMigrator` application again to fill it with the new configuration. +2. Then you can simply clear all the **data** in these tables then execute the `DbMigrator` application to fill it with the new configuration. #### Migrating the Configuration Data -If you've customize your IdentityServer configuration in the database or in the seed data, you should understand the changes and upgrade your code/data accordingly. Especially, the following changes will affect your application: +If you've customized your IdentityServer configuration in the database or in the seed data, you should understand the changes and upgrade your code/data accordingly. Especially, the following changes will affect your application: -- `IdentityServerApiScopes` table's `Enabled` field is dropped and re-created. So, you need to manually enable the api scopes again. +- `IdentityServerApiScopes` table's `Enabled` field is dropped and re-created. So, you need to enable the API scopes again manually. - `IdentityServerApiResourceScopes` table is dropped and recreated. So, you need to backup and move your current data to the new table. - `IdentityServerIdentityResourceClaims` table is dropped and recreated. So, you need to backup and move your current data to the new table. @@ -98,9 +115,9 @@ You may need to perform additional steps based on how much you made custom confi ### Other IdentityServer Changes -IdentityServer has removed the [public origin option](https://github.com/IdentityServer/IdentityServer4/pull/4335). It was resolving http/https conversion problems, but they decided to leave this to the developer. This is especially needed if you use a reverse proxy where your external protocol is HTTPS but internal protocol is HTTP. +IdentityServer has removed the [public origin option](https://github.com/IdentityServer/IdentityServer4/pull/4335). It was resolving HTTP/HTTPS conversion issues, but they decided to leave this to the developer. This is especially needed if you use a reverse proxy where your external protocol is HTTPS but internal protocol is HTTP. -One simple solution is to add such a middleware into your ASP.NET Core pipeline, at the beginning. +One simple solution is to add such a middleware at the begingning of your ASP.NET Core pipeline. ```csharp app.Use((httpContext, next) => @@ -117,9 +134,101 @@ app.Use((httpContext, next) => - https://leastprivilege.com/2020/06/19/announcing-identityserver4-v4-0/ - https://github.com/IdentityServer/IdentityServer4/issues/4592 -## Blazor UI +## Auto API Controller Route Changes + +The route calculation for the [Auto API Controllers](https://docs.abp.io/en/abp/latest/API/Auto-API-Controllers) is changing with the ABP Framework version 4.0 ([#5325](https://github.com/abpframework/abp/issues/5325)). Before v4.0 the route paths were **camelCase**. After version 4.0, it's changed to **kebab-case** route paths where it is possible. -### AbpCrudPageBase Changes +**A typical auto API before v4.0** + +![route-before-4](images/route-before-4.png) + +**camelCase route parts become kebab-case with 4.0** + +![route-4](images/route-4.png) + +### How to Fix? + +You may not take any action for the MVC & Blazor UI projects. + +For the Angular UI, this change may effect your client UI. If you have used the [ABP CLI Service Proxy Generation](../UI/Angular/Service-Proxies.md), you can run the server side and re-generate the service proxies. If you haven't used this tool, you should manually update the related URLs in your application. + +If there are other type of clients (e.g. 3rd-party companies) using your APIs, they also need to update the URLs. + +### Use the v3.x style URLs + +If it is hard to change it in your application, you can still to use the version 3.x route strategy, by following one of the approaches; + +- Set `UseV3UrlStyle` to `true` in the options of the `options.ConventionalControllers.Create(...)` method. Example: + +```csharp +options.ConventionalControllers + .Create(typeof(BookStoreApplicationModule).Assembly, opts => + { + opts.UseV3UrlStyle = true; + }); +``` + +This approach affects only the controllers for the `BookStoreApplicationModule`. + +- Set `UseV3UrlStyle` to `true` for the `AbpConventionalControllerOptions` to set it globally. Example: + +```csharp +Configure(options => +{ + options.UseV3UrlStyle = true; +}); +``` + +Setting it globally affects all the modules in a modular application. + +## Removed Retry for the Dynamic HTTP Client Proxies + +[Dynamic C# HTTP Client Proxies](../API/Dynamic-CSharp-API-Clients.md) were trying up to 3 times if a request fails using the [Polly](https://github.com/App-vNext/Polly) library. Starting from the version 4.0, this logic has been removed. If you need it, you should configure it in your own application, by configuring the `AbpHttpClientBuilderOptions` in the `PreConfigureServices` method of your module. + +**Example: Retry 3 times on failure by incremental waiting between tries** + +````csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(options => + { + options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => + { + clientBuilder.AddTransientHttpErrorPolicy( + policyBuilder => policyBuilder + .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i))) + ); + }); + }); +} +```` + +This example uses the Microsoft.Extensions.Http.Polly NuGet package. + +If you create a new solution, you can find the same configuration in the `.HttpApi.Client.ConsoleTestApp` project's module class, as an example. + +## Creation Audit Properties Made Read-Only + +Removed setters from the `IHasCreationTime.CreationTime`, ` IMustHaveCreator.CreatorId` and `IMayHaveCreator.CreatorId` properties to accidently set the creation properties while updating an existing entity. + +Since the ABP Framework automatically sets these properties, you normally don't need to directly set them. If you want to set them, as a best practice, it is suggested to make it in the constructor to not provide a way to change it later. + +These properties implemented with `protected set` in the `Entity` and `AggregateRoot` base classes. That means you can still set in a derived class, if you need it. Alternatively, you can use reflection to set them (Or use `ObjectHelper.TrySetProperty` which internally uses reflection) out of the class if you have to do. + +## Changed type of the IHasExtraProperties.ExtraProperties + +`IHasExtraProperties.ExtraProperties` was a regular `Dictionary`. With the version 4.0, it is replaced with `ExtraPropertyDictionary` class which inherits the `Dictionary`. + +Most of the applications don't be affected by this change. If you've directly implemented this interface, replace the standard dictionary the the `ExtraPropertyDictionary`. + +## ASP.NET Core MVC / Razor Pages UI + +See the [ASP.NET Core MVC / Razor Pages UI Migration Guide](Abp-4_0-MVC-Razor-Pages.md). + +## Angular UI + +See the [Angular UI Migration Guide](Abp-4_0-Angular.md). + +## Blazor UI -- `OpenEditModalAsync` method is requires `EntityDto` instead of id (`Guid`) parameter. -- `DeleteEntityAsync` method doesn't display confirmation dialog anymore. You can use the new `EntityActions` component in DataGrids to show confirmation messages. You can also inject `IUiMessageService` to your page or component and call `ConfirmAsync` explicitly. +See the [Blazor UI Migration Guide](Abp-4_0-Blazor.md). \ No newline at end of file diff --git a/docs/en/Road-Map.md b/docs/en/Road-Map.md index 8670ba080b..3a45ca1767 100644 --- a/docs/en/Road-Map.md +++ b/docs/en/Road-Map.md @@ -1,17 +1,12 @@ # ABP Framework Road Map -You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones) for a detailed road map. - -While we will **continue to add other exciting features**, we will work on the following major items in the **middle term**: - -* **Blazor UI** for the framework and all the pre-built modules (in progress). -* **.NET 5.0**! As Microsoft has announced that the .NET 5.0 will be released in November 2020, we will prepare for this change before and move to the .NET 5.0 just after Microsoft releases it. We hope a smooth transition. - -Beside this middle term goals, there are many features in the [backlog](https://github.com/abpframework/abp/milestone/2). Here, a list of some major items in the backlog; +You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones) for a detailed road map. Here, a list of some major items in the backlog; * [#2882](https://github.com/abpframework/abp/issues/2882) / Providing a gRPC integration infrastructure (while it is [already possible](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) to create or consume gRPC endpoints for your application, we plan to create endpoints for the [standard application modules](https://docs.abp.io/en/abp/latest/Modules/Index)) * [#236](https://github.com/abpframework/abp/issues/236) Resource based authorization system +* [#6132](https://github.com/abpframework/abp/issues/6132) A New Theme alternative to the Basic Theme * [#1754](https://github.com/abpframework/abp/issues/1754) / Multi-lingual entities +* [#497](https://github.com/abpframework/abp/issues/497) API Versioning system finalize & document * [#633](https://github.com/abpframework/abp/issues/633) / Realtime notification system * [#57](https://github.com/abpframework/abp/issues/57) / Built-in CQRS infrastructure * [#336](https://github.com/abpframework/abp/issues/336) / Health Check abstraction diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index cf0041ab72..6df847d6d0 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -547,7 +547,3 @@ The final UI looks like below: ## See Also - [How to Replace PermissionManagementComponent](./Permission-Management-Component-Replacement.md) - -## What's Next? - -- [Custom Setting Page](./Custom-Setting-Page.md) diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md index b1a882d43a..8014cd9b14 100644 --- a/docs/en/UI/Angular/Config-State.md +++ b/docs/en/UI/Angular/Config-State.md @@ -190,7 +190,3 @@ Please refer to `Config.Environment` type for all the properties you can pass to - [Settings](./Settings.md) - [Features](./Features.md) - -## What's Next? - -- [HTTP Requests](./Http-Requests) diff --git a/docs/en/UI/Angular/Confirmation-Service.md b/docs/en/UI/Angular/Confirmation-Service.md index 16dfa9fd94..ef48332580 100644 --- a/docs/en/UI/Angular/Confirmation-Service.md +++ b/docs/en/UI/Angular/Confirmation-Service.md @@ -180,8 +180,3 @@ clear( ``` - `status` parameter is the value of the confirmation closing event. - - -## What's Next? - -- [Toast Overlay](./Toaster-Service.md) diff --git a/docs/en/UI/Angular/Content-Projection-Service.md b/docs/en/UI/Angular/Content-Projection-Service.md index 7e03776835..ee64f57e20 100644 --- a/docs/en/UI/Angular/Content-Projection-Service.md +++ b/docs/en/UI/Angular/Content-Projection-Service.md @@ -71,8 +71,3 @@ projectContent | TemplateRef>( - `projectionStrategy` parameter is the primary focus here and is explained above. - `injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`. - - -## What's Next? - -- [Confirmation Popup](./Confirmation-Service.md) diff --git a/docs/en/UI/Angular/Cross-Origin-Strategy.md b/docs/en/UI/Angular/Cross-Origin-Strategy.md index e626bf5011..5edcbed9e6 100644 --- a/docs/en/UI/Angular/Cross-Origin-Strategy.md +++ b/docs/en/UI/Angular/Cross-Origin-Strategy.md @@ -51,10 +51,3 @@ CROSS_ORIGIN_STRATEGY.UseCredentials(integrity?: string) ``` `crossorigin` will be set as `"use-credentials"` and `integrity` is optional. - - - - -## What's Next? - -- [LoadingStrategy](./Loading-Strategy.md) diff --git a/docs/en/UI/Angular/Dom-Insertion-Service.md b/docs/en/UI/Angular/Dom-Insertion-Service.md index d56e4f8b0e..78e01c59fb 100644 --- a/docs/en/UI/Angular/Dom-Insertion-Service.md +++ b/docs/en/UI/Angular/Dom-Insertion-Service.md @@ -134,7 +134,3 @@ has(content: string): boolean The `has` method returns a boolean value that indicates the given content has already been added to the DOM or not. - `content` parameter is the content of the inserted `HTMLScriptElement` or `HTMLStyleElement` element. - -## What's Next? - -- [Lazy Loading Scripts & Styles](./Lazy-Load-Service.md) diff --git a/docs/en/UI/Angular/Environment.md b/docs/en/UI/Angular/Environment.md index 1b78a11cc6..04bf8f98c4 100644 --- a/docs/en/UI/Angular/Environment.md +++ b/docs/en/UI/Angular/Environment.md @@ -101,8 +101,3 @@ export interface RemoteEnv { * `customMergeFn`: You can also provide your own merge function as shown in the example. It will take two parameters, `localEnv: Partial` and `remoteEnv` and it needs to return a `Config.Environment` object. * `method`: HTTP method to be used when retrieving environment config. Default: `GET` * `headers`: If extra headers are needed for the request, it can be set through this field. - - -## What's Next? - -- [About Feature Libraries](./Feature-Libraries.md) diff --git a/docs/en/UI/Angular/Feature-Libraries.md b/docs/en/UI/Angular/Feature-Libraries.md index 937bae7245..bdfff93371 100644 --- a/docs/en/UI/Angular/Feature-Libraries.md +++ b/docs/en/UI/Angular/Feature-Libraries.md @@ -90,9 +90,3 @@ Depending on the library, the `.forLazy` static method may also receive some opt --- 1 _Libraries expect to work at a predefined path. Please check [how to patch a navigation element](./Modifying-the-Menu.md#how-to-patch-or-remove-a-navigation-element), if you want to use a different path from the default one (e.g. '/identity')._ [↩](#a-modify-route) - ---- - -## What's Next? - -- [Service Proxies](./Service-Proxies.md) diff --git a/docs/en/UI/Angular/Features.md b/docs/en/UI/Angular/Features.md index 930fc2e527..3278bfd988 100644 --- a/docs/en/UI/Angular/Features.md +++ b/docs/en/UI/Angular/Features.md @@ -31,7 +31,3 @@ const defaultLang = this.config.getFeature("Identity.TwoFactor"); ``` You can then check the value of the feature to perform your logic. Please note that **feature keys are case-sensitive**. - -## What's Next? - -- [Permission Management](./Permission-Management.md) diff --git a/docs/en/UI/Angular/Form-Validation.md b/docs/en/UI/Angular/Form-Validation.md new file mode 100644 index 0000000000..38e7969eea --- /dev/null +++ b/docs/en/UI/Angular/Form-Validation.md @@ -0,0 +1,140 @@ +# Form Validation + +Reactive forms in ABP Angular UI are validated by [ngx-validate](https://www.npmjs.com/package/@ngx-validate/core) and helper texts are shown automatically based on validation rules and error blueprints. You do not have to add any elements or components to your templates. The library handles that for you. Here is how the experience is: + +The ngx-validate library validates an Angular reactive form and an error text appears under each wrong input based on the validation rule and the error blueprint. + +## How to Add New Error Messages + +You can add a new error message by providing the `VALIDATION_BLUEPRINTS` injection token from your root module. + +```js +import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core"; + +@NgModule({ + // rest of the module metadata + + providers: [ + // other providers + { + provide: VALIDATION_BLUEPRINTS, + useValue: { + uniqueUsername: "::AlreadyExists[{%{{{ username }}}%}]", + }, + }, + ], +}) +export class AppModule {} +``` + +When a [validator](https://angular.io/guide/form-validation#defining-custom-validators) or an [async validator](https://angular.io/guide/form-validation#creating-asynchronous-validators) returns an error with the key given to the error blueprints (`uniqueUsername` here), the validation library will be able to display an error message after localizing according to the given key and interpolation params. The result will look like this: + +An already taken username is entered while creating new user and a custom error message appears under the input after validation. + +In this example; + +- Localization key is `::AlreadyExists`. +- The interpolation param is `username`. +- Localization resource is defined as `"AlreadyExists": "Sorry, “{0}” already exists."`. +- And the validator should return `{ uniqueUsername: { username: "admin" } }` as the error object. + +## How to Change Existing Error Messages + +You can overwrite an existing error message by providing `VALIDATION_BLUEPRINTS` injection token from your root module. Let's imagine you have a custom localization resource for required inputs. + +```json +"RequiredInput": "Oops! We need this input." +``` + +To use this instead of the built-in required input message, all you need to do is the following. + +```js +import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core"; + +@NgModule({ + // rest of the module metadata + + providers: [ + // other providers + { + provide: VALIDATION_BLUEPRINTS, + useValue: { + required: "::RequiredInput", + }, + }, + ], +}) +export class AppModule {} +``` + +The error message will look like this: + +A required field is cleared and the custom error message appears under the input. + +## How to Disable Validation on a Form + +If you want to validate a form manually, you can always disable automatic validation on it. All you need to do is place `skipValidation` on the form element. + +```html +
+ +
+``` + +## How to Disable Validation on a Specific Field + +Validation works on any element or component with a `formControl` or `formControlName` directive. You can disable automatic validation on a specific field by placing `skipValidation` on the input element or component. + +```html + +``` + +## How to Use a Custom Error Component + +First, build a custom error component. Extending the existing `ValidationErrorComponent` would make it easier. + +```js +import { ValidationErrorComponent } from "@abp/ng.theme.basic"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; + +@Component({ + selector: "app-validation-error", + template: ` +
+ {%{{{ error.message | abpLocalization: error.interpoliteParams }}}%} +
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ErrorComponent extends ValidationErrorComponent {} +``` + +Then, declare and provide it in your root module. + +```js +import { VALIDATION_ERROR_TEMPLATE } from "@ngx-validate/core"; + +@NgModule({ + // rest of the module metadata + + declarations: [ + // other declarables + ErrorComponent, + ], + providers: [ + // other providers + { + provide: VALIDATION_ERROR_TEMPLATE, + useValue: ErrorComponent, + }, + ], +}) +export class AppModule {} +``` + +The error message will be bold and italic now: + +A required field is cleared and a bold and italic error message appears. diff --git a/docs/en/UI/Angular/HTTP-Requests.md b/docs/en/UI/Angular/HTTP-Requests.md index 2ac5aa1b49..ab1ac04097 100644 --- a/docs/en/UI/Angular/HTTP-Requests.md +++ b/docs/en/UI/Angular/HTTP-Requests.md @@ -203,7 +203,3 @@ getSomeCustomHeaderValue() { You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10). - -## What's Next? - -* [Localization](./Localization.md) diff --git a/docs/en/UI/Angular/Lazy-Load-Service.md b/docs/en/UI/Angular/Lazy-Load-Service.md index 344b446107..0e1f7928c7 100644 --- a/docs/en/UI/Angular/Lazy-Load-Service.md +++ b/docs/en/UI/Angular/Lazy-Load-Service.md @@ -204,10 +204,3 @@ load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Obser - `strategy` parameter is the primary focus here and is explained above. - `retryTimes` defines how many times the loading will be tried again before fail (_default: 2_). - `retryDelay` defines how much delay there will be between retries (_default: 1000_). - - - - -## What's Next? - -- [Projecting Angular Content](./Content-Projection-Service.md) diff --git a/docs/en/UI/Angular/List-Service.md b/docs/en/UI/Angular/List-Service.md index d534558363..7cc31a46fd 100644 --- a/docs/en/UI/Angular/List-Service.md +++ b/docs/en/UI/Angular/List-Service.md @@ -177,8 +177,3 @@ As of v3.0, with ngx-datatable, the `page` property has to be set as `0` for ini ``` **Important Note:** The `abp-table` is not removed, but is deprecated and will be removed in the future. Please consider switching to ngx-datatable. - - -## What's Next? - -- [Easy *ngFor trackBy](./Track-By-Service.md) diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index 1fe4d5067a..0c43317728 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -309,15 +309,13 @@ import { registerLocale } from './register-locale'; ] ``` -After this custom `registerLocale` function, only en and fr locale files will be created as chunks: +After this custom `registerLocale` function, since the en and fr added to the `webpackInclude`, only en and fr locale files will be created as chunks: ![locale chunks](https://user-images.githubusercontent.com/34455572/98203212-acaa2100-1f44-11eb-85af-4eb66d296326.png) +Which locale files you add to `webpackInclude` magic comment, they will be included in the bundle + ## See Also * [Localization in ASP.NET Core](../../Localization.md) - -## What's Next? - -* [Settings](./Settings.md) diff --git a/docs/en/UI/Angular/Migration-Guide-v3.md b/docs/en/UI/Angular/Migration-Guide-v3.md index 6a7b86efcb..f13b0c966f 100644 --- a/docs/en/UI/Angular/Migration-Guide-v3.md +++ b/docs/en/UI/Angular/Migration-Guide-v3.md @@ -470,8 +470,3 @@ Some interfaces have long been marked as deprecated and now they are removed. - Please check if you are still using [anything listed in this issue](https://github.com/abpframework/abp/issues/4281) - -## What's Next? - -* [Quick Start](./Quick-Start.md) - diff --git a/docs/en/UI/Angular/Modifying-the-Menu.md b/docs/en/UI/Angular/Modifying-the-Menu.md index a3c356891e..b0a3b760fa 100644 --- a/docs/en/UI/Angular/Modifying-the-Menu.md +++ b/docs/en/UI/Angular/Modifying-the-Menu.md @@ -271,8 +271,3 @@ export class AppComponent { * Patched the languages dropdown element with new `requiredPolicy` and new `order`. * Removed the current user dropdown element. - - -## What's Next - -* [Component Replacement](./Component-Replacement.md) diff --git a/docs/en/UI/Angular/Multi-Tenancy.md b/docs/en/UI/Angular/Multi-Tenancy.md index c3e54c5f83..e7e284bb0d 100644 --- a/docs/en/UI/Angular/Multi-Tenancy.md +++ b/docs/en/UI/Angular/Multi-Tenancy.md @@ -126,7 +126,3 @@ The app sends the `__tenant` header that contains the current tenant id on each ## See Also - [Multi Tenancy in ABP](../../Multi-Tenancy.md) - -## What's Next? - -- [Managing RxJS Subscriptions](./Subscription-Service.md) diff --git a/docs/en/UI/Angular/PWA-Configuration.md b/docs/en/UI/Angular/PWA-Configuration.md index a6aee244ba..918aa4970d 100644 --- a/docs/en/UI/Angular/PWA-Configuration.md +++ b/docs/en/UI/Angular/PWA-Configuration.md @@ -340,7 +340,3 @@ In case you want to cache other static files, please refer to the [service worke ### 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) diff --git a/docs/en/UI/Angular/Permission-Management.md b/docs/en/UI/Angular/Permission-Management.md index a6d7d13c14..adcbafdd99 100644 --- a/docs/en/UI/Angular/Permission-Management.md +++ b/docs/en/UI/Angular/Permission-Management.md @@ -57,8 +57,4 @@ const routes: Routes = [ ]; ``` -Granted Policies are stored in the `auth` property of `ConfigState`. - -## What's Next? - -* [Multi Tenancy](./Multi-Tenancy.md) +Granted Policies are stored in the `auth` property of `ConfigState`. \ No newline at end of file diff --git a/docs/en/UI/Angular/Quick-Start.md b/docs/en/UI/Angular/Quick-Start.md index a58671156c..c17f069c5f 100644 --- a/docs/en/UI/Angular/Quick-Start.md +++ b/docs/en/UI/Angular/Quick-Start.md @@ -204,9 +204,3 @@ In addition, you can [deploy your application to certain targets using the Angul --- 1 _The compiled output will be placed under `/dist` in a folder by the project name._ [↩](#a-dist-folder-name) - ---- - -## What's Next? - -- [Environment Variables](./Environment.md) diff --git a/docs/en/UI/Angular/Service-Proxies.md b/docs/en/UI/Angular/Service-Proxies.md index 92c4b01871..8f6b1c4d60 100644 --- a/docs/en/UI/Angular/Service-Proxies.md +++ b/docs/en/UI/Angular/Service-Proxies.md @@ -137,7 +137,3 @@ export class BookComponent implements OnInit { ``` > Please [see this article](https://github.com/abpframework/abp/blob/dev/docs/en/Blog-Posts/2020-09-07%20Angular-Service-Proxies/POST.md) to learn more about service proxies. - -## What's Next? - -- [PWA Configuration](./PWA-Configuration.md) diff --git a/docs/en/UI/Angular/Settings.md b/docs/en/UI/Angular/Settings.md index f3a5775838..0dd29025e5 100644 --- a/docs/en/UI/Angular/Settings.md +++ b/docs/en/UI/Angular/Settings.md @@ -53,7 +53,3 @@ const localizationSettings = this.config.getSettings("Localization"); ``` Beware though, **settings search is case-sensitive**. - -## What's Next? - -- [Features](./Features.md) diff --git a/docs/en/UI/Angular/Subscription-Service.md b/docs/en/UI/Angular/Subscription-Service.md index dee77cbf1e..e080f8f3fa 100644 --- a/docs/en/UI/Angular/Subscription-Service.md +++ b/docs/en/UI/Angular/Subscription-Service.md @@ -197,7 +197,3 @@ class DemoComponent implements OnInit { } } ``` - -## What's Next? - -- [Working with Lists](./List-Service.md) diff --git a/docs/en/UI/Angular/Toaster-Service.md b/docs/en/UI/Angular/Toaster-Service.md index 4d94b8cf6d..059fd720a8 100644 --- a/docs/en/UI/Angular/Toaster-Service.md +++ b/docs/en/UI/Angular/Toaster-Service.md @@ -249,7 +249,3 @@ Removes all open toasts. ## See Also - [Confirmation Popup](./Confirmation-Service.md) - -## What's Next? - -- [Modifying the Menu](./Modifying-the-Menu.md) diff --git a/docs/en/UI/Angular/Track-By-Service.md b/docs/en/UI/Angular/Track-By-Service.md index 7883be4754..a7df7b80b6 100644 --- a/docs/en/UI/Angular/Track-By-Service.md +++ b/docs/en/UI/Angular/Track-By-Service.md @@ -111,9 +111,3 @@ class DemoComponent { trackByTenantAccountId = trackByDeep('tenant', 'account', 'id'); } ``` - - - -## What's Next? - -- [Inserting Scripts & Styles to DOM](./Dom-Insertion-Service.md) diff --git a/docs/en/UI/Angular/images/form-validation---custom-error-template.gif b/docs/en/UI/Angular/images/form-validation---custom-error-template.gif new file mode 100644 index 0000000000..6d9f492ce4 Binary files /dev/null and b/docs/en/UI/Angular/images/form-validation---custom-error-template.gif differ diff --git a/docs/en/UI/Angular/images/form-validation---error-display-user-experience.gif b/docs/en/UI/Angular/images/form-validation---error-display-user-experience.gif new file mode 100644 index 0000000000..9070a0f730 Binary files /dev/null and b/docs/en/UI/Angular/images/form-validation---error-display-user-experience.gif differ diff --git a/docs/en/UI/Angular/images/form-validation---new-error-message.gif b/docs/en/UI/Angular/images/form-validation---new-error-message.gif new file mode 100644 index 0000000000..ae61215f61 Binary files /dev/null and b/docs/en/UI/Angular/images/form-validation---new-error-message.gif differ diff --git a/docs/en/UI/Angular/images/form-validation---overwrite-error-message.gif b/docs/en/UI/Angular/images/form-validation---overwrite-error-message.gif new file mode 100644 index 0000000000..b6af5a68b7 Binary files /dev/null and b/docs/en/UI/Angular/images/form-validation---overwrite-error-message.gif differ diff --git a/docs/en/UI/AspNetCore/Overall.md b/docs/en/UI/AspNetCore/Overall.md index a499cd93a3..8cd9dd3f5e 100644 --- a/docs/en/UI/AspNetCore/Overall.md +++ b/docs/en/UI/AspNetCore/Overall.md @@ -50,7 +50,7 @@ There are a set of standard JavaScript/CSS libraries that comes pre-installed an - [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. - [SweetAlert](https://sweetalert.js.org/) to show fancy alert message and confirmation dialogs. - [Toastr](https://github.com/CodeSeven/toastr) to show toast notifications. -- [Lodesh](https://lodash.com/) as a utility library. +- [Lodash](https://lodash.com/) as a utility library. - [Luxon](https://moment.github.io/luxon/) for date/time operations. - [JQuery Form](https://github.com/jquery-form/form) for AJAX forms. - [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers. @@ -156,4 +156,4 @@ ABP Framework provides a lot of built-in solutions to common application require ## Customization -There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](Customization-User-Interface.md). \ No newline at end of file +There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](Customization-User-Interface.md). diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index e6bbf6a3b0..217badae8e 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -636,6 +636,10 @@ "text": "Localization", "path": "UI/Angular/Localization.md" }, + { + "text": "Form Validation", + "path": "UI/Angular/Form-Validation.md" + }, { "text": "Settings", "path": "UI/Angular/Settings.md" diff --git a/docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md b/docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md index 2e950d4355..b8abe99cdc 100644 --- a/docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md +++ b/docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md @@ -59,7 +59,6 @@ context.Services.Replace( ### 示例: 重写服务方法 ````csharp -//[RemoteService(IsEnabled = false)] // 如果你在使用动态控制器,为了避免为应用服务创建重复的控制器, 你可以禁用远程访问. [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(MyIdentityUserAppService))] public class MyIdentityUserAppService : IdentityUserAppService diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj index 97b9c6dc0a..f138f313c2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj index bac9441f00..84ecc6a1bf 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj @@ -9,7 +9,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj index 6d3b666478..508991e6bc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj @@ -21,8 +21,8 @@ - - + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs index b24a1d8cb5..ca3a2be9c5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs @@ -31,11 +31,11 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling if (errorInfo.Details.IsNullOrEmpty()) { //TODO: Should we introduce MessageService.Error (sync) method instead of such a usage (without await)..? - MessageService.ErrorAsync(errorInfo.Message); + MessageService.Error(errorInfo.Message); } else { - MessageService.ErrorAsync(errorInfo.Details, errorInfo.Message); + MessageService.Error(errorInfo.Details, errorInfo.Message); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Messages/SimpleUiMessageService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Messages/SimpleUiMessageService.cs index f7dcd8f7ab..3218912ad0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Messages/SimpleUiMessageService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Messages/SimpleUiMessageService.cs @@ -15,27 +15,27 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.Messages JsRuntime = jsRuntime; } - public async Task InfoAsync(string message, string title = null, Action options = null) + public async Task Info(string message, string title = null, Action options = null) { await JsRuntime.InvokeVoidAsync("alert", message); } - public async Task SuccessAsync(string message, string title = null, Action options = null) + public async Task Success(string message, string title = null, Action options = null) { await JsRuntime.InvokeVoidAsync("alert", message); } - public async Task WarnAsync(string message, string title = null, Action options = null) + public async Task Warn(string message, string title = null, Action options = null) { await JsRuntime.InvokeVoidAsync("alert", message); } - public async Task ErrorAsync(string message, string title = null, Action options = null) + public async Task Error(string message, string title = null, Action options = null) { await JsRuntime.InvokeVoidAsync("alert", message); } - public async Task ConfirmAsync(string message, string title = null, Action options = null) + public async Task Confirm(string message, string title = null, Action options = null) { return await JsRuntime.InvokeAsync("confirm", message); } diff --git a/framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj b/framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj index 973f49118c..407a9141f0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj @@ -21,7 +21,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/Messages/IUiMessageService.cs b/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/Messages/IUiMessageService.cs index 85191c4fd0..4f43cb90d6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/Messages/IUiMessageService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/Messages/IUiMessageService.cs @@ -5,14 +5,14 @@ namespace Volo.Abp.AspNetCore.Components.Messages { public interface IUiMessageService { - Task InfoAsync(string message, string title = null, Action options = null); + Task Info(string message, string title = null, Action options = null); - Task SuccessAsync(string message, string title = null, Action options = null); + Task Success(string message, string title = null, Action options = null); - Task WarnAsync(string message, string title = null, Action options = null); + Task Warn(string message, string title = null, Action options = null); - Task ErrorAsync(string message, string title = null, Action options = null); + Task Error(string message, string title = null, Action options = null); - Task ConfirmAsync(string message, string title = null, Action options = null); + Task Confirm(string message, string title = null, Action options = null); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/Notifications/IUiNotificationService.cs b/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/Notifications/IUiNotificationService.cs index cef898b179..dfbde9b1eb 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/Notifications/IUiNotificationService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/Notifications/IUiNotificationService.cs @@ -6,8 +6,11 @@ namespace Volo.Abp.AspNetCore.Components.Notifications public interface IUiNotificationService { Task Info(string message, string title = null, Action options = null); + Task Success(string message, string title = null, Action options = null); + Task Warn(string message, string title = null, Action options = null); + Task Error(string message, string title = null, Action options = null); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/VeeValidate/VeeValidateScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/VeeValidate/VeeValidateScriptContributor.cs new file mode 100644 index 0000000000..7511c677cc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/VeeValidate/VeeValidateScriptContributor.cs @@ -0,0 +1,15 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Vue; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.VeeValidate +{ + [DependsOn(typeof(VueScriptContributor))] + public class VeeValidateScriptContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add("/libs/vee-validate.js/vee-validate.full.js"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Vue/VueScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Vue/VueScriptContributor.cs new file mode 100644 index 0000000000..e2cc9137ba --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Vue/VueScriptContributor.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Vue +{ + public class VueScriptContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add("/libs/vue.js/vue.js"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj index 3d20feea08..a6e7d3d9ae 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj @@ -26,7 +26,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj index a018dbbffd..be27355fde 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj @@ -27,8 +27,8 @@ - - + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs index 4b63828997..a48d5148bd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -23,6 +24,9 @@ namespace Volo.Abp.AspNetCore.Mvc.Json options.JsonSerializerOptions.Converters.Add(ServiceProvider.GetRequiredService()); options.JsonSerializerOptions.Converters.Add(ServiceProvider.GetRequiredService()); + + options.JsonSerializerOptions.Converters.Add(new AbpStringToEnumConverter()); + options.JsonSerializerOptions.Converters.Add(new AbpStringToBooleanConverter()); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs index 9ff3ac21b0..b4e19e6ae7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using System.Text.Encodings.Web; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; +using Volo.Abp.Json; namespace Volo.Abp.AspNetCore.Mvc.Json { @@ -15,6 +16,14 @@ namespace Volo.Abp.AspNetCore.Mvc.Json { public static IMvcCoreBuilder AddAbpHybridJson(this IMvcCoreBuilder builder) { + var abpJsonOptions = builder.Services.ExecutePreConfiguredActions(); + if (!abpJsonOptions.UseHybridSerializer) + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, AbpMvcNewtonsoftJsonOptionsSetup>()); + builder.AddNewtonsoftJson(); + return builder; + } + //SystemTextJsonInputFormatter builder.Services.AddTransient(provider => { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs index b6d56b2f2a..2b38c4d484 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs @@ -18,7 +18,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Response await next(); - if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK) + if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK + && context.Result == null) { var returnType = context.ActionDescriptor.GetReturnType(); if (returnType == typeof(Task) || returnType == typeof(void)) diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj index d571bf4d72..1525a2f798 100644 --- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj +++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj @@ -24,7 +24,7 @@ - + diff --git a/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj b/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj index edfe716335..f7f1940c0f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj +++ b/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj index ae61d6b870..ac294593a7 100644 --- a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj +++ b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj @@ -17,7 +17,7 @@ - + diff --git a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiMessageService.cs b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiMessageService.cs index c3b73c9941..ffffd5ed1b 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiMessageService.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiMessageService.cs @@ -30,7 +30,7 @@ namespace Volo.Abp.BlazoriseUI Logger = NullLogger.Instance; } - public Task InfoAsync(string message, string title = null, Action options = null) + public Task Info(string message, string title = null, Action options = null) { var uiMessageOptions = CreateDefaultOptions(); options?.Invoke(uiMessageOptions); @@ -40,7 +40,7 @@ namespace Volo.Abp.BlazoriseUI return Task.CompletedTask; } - public Task SuccessAsync(string message, string title = null, Action options = null) + public Task Success(string message, string title = null, Action options = null) { var uiMessageOptions = CreateDefaultOptions(); options?.Invoke(uiMessageOptions); @@ -50,7 +50,7 @@ namespace Volo.Abp.BlazoriseUI return Task.CompletedTask; } - public Task WarnAsync(string message, string title = null, Action options = null) + public Task Warn(string message, string title = null, Action options = null) { var uiMessageOptions = CreateDefaultOptions(); options?.Invoke(uiMessageOptions); @@ -60,7 +60,7 @@ namespace Volo.Abp.BlazoriseUI return Task.CompletedTask; } - public Task ErrorAsync(string message, string title = null, Action options = null) + public Task Error(string message, string title = null, Action options = null) { var uiMessageOptions = CreateDefaultOptions(); options?.Invoke(uiMessageOptions); @@ -70,7 +70,7 @@ namespace Volo.Abp.BlazoriseUI return Task.CompletedTask; } - public Task ConfirmAsync(string message, string title = null, Action options = null) + public Task Confirm(string message, string title = null, Action options = null) { var uiMessageOptions = CreateDefaultOptions(); options?.Invoke(uiMessageOptions); diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor.cs index 06d47f9a03..e3e7112afa 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor.cs @@ -56,7 +56,7 @@ namespace Volo.Abp.BlazoriseUI.Components { if (ConfirmationMessage != null) { - if (await UiMessageService.ConfirmAsync(ConfirmationMessage())) + if (await UiMessageService.Confirm(ConfirmationMessage())) { await Clicked.InvokeAsync(); } diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj index faadaff04c..d275729d40 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -12,9 +12,9 @@ - - - + + + diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj index 48304420e6..4b3d4dcff7 100644 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj b/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj index 757d95d2bc..f1830d9d98 100644 --- a/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj +++ b/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs index 5b4180d989..26574c1c68 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System.Threading.Tasks; using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; using Volo.Abp.Cli.ProjectModification; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; @@ -37,7 +38,10 @@ namespace Volo.Abp.Cli.Commands } var newTemplate = commandLineArgs.Options.ContainsKey(Options.NewTemplate.Long); - var newProTemplate = commandLineArgs.Options.ContainsKey(Options.NewProTemplate.Long); + + var template = commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long); + var newProTemplate = !string.IsNullOrEmpty(template) && template == ModuleProTemplate.TemplateName; + var withSourceCode = newTemplate || newProTemplate || commandLineArgs.Options.ContainsKey(Options.SourceCode.Long); var addSourceCodeToSolutionFile = withSourceCode && commandLineArgs.Options.ContainsKey("add-to-solution-file"); @@ -91,8 +95,8 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(" abp add-module Volo.Blogging -s Acme.BookStore Adds the module to the given solution."); sb.AppendLine(" abp add-module Volo.Blogging -s Acme.BookStore --skip-db-migrations false Adds the module to the given solution but doesn't create a database migration."); sb.AppendLine(@" abp add-module Volo.Blogging -s Acme.BookStore -sp ..\Acme.BookStore.Web\Acme.BookStore.Web.csproj Adds the module to the given solution and specify migration startup project."); - sb.AppendLine(@" abp add-module ProductManagement --new-template -sp ..\Acme.BookStore.Web\Acme.BookStore.Web.csproj Crates a new module named `ProductManagement` and adds it to your solution."); - sb.AppendLine(@" abp add-module ProductManagement --new-template --add-to-solution-file -sp ..\Acme.BookStore.Web\Acme.BookStore.Web.csproj Crates a new module named `ProductManagement`, adds it to your solution & solution file."); + sb.AppendLine(@" abp add-module ProductManagement --new -sp ..\Acme.BookStore.Web\Acme.BookStore.Web.csproj Crates a new module named `ProductManagement` and adds it to your solution."); + sb.AppendLine(@" abp add-module ProductManagement --new --add-to-solution-file -sp ..\Acme.BookStore.Web\Acme.BookStore.Web.csproj Crates a new module named `ProductManagement`, adds it to your solution & solution file."); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); @@ -176,12 +180,13 @@ namespace Volo.Abp.Cli.Commands public class NewTemplate { - public const string Long = "new-template"; + public const string Long = "new"; } - public class NewProTemplate + public class Template { - public const string Long = "new-pro-template"; + public const string Short = "t"; + public const string Long = "template"; } } } diff --git a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj index 79760820d8..f41c4c3398 100644 --- a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj +++ b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj index 4414eed1c6..1f3f28128f 100644 --- a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj +++ b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj @@ -16,15 +16,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj index ff0e06ebae..1f3f2e0140 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj index 010f419374..181abe63bf 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj index a123570984..711f9bff11 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo.Abp.ExceptionHandling.csproj b/framework/src/Volo.Abp.ExceptionHandling/Volo.Abp.ExceptionHandling.csproj index d0aa06056e..01f3828f50 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo.Abp.ExceptionHandling.csproj +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo.Abp.ExceptionHandling.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj index 95270edc7c..269e6a6c18 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs b/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs index 4d79779dd1..84b58d6ee0 100644 --- a/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs +++ b/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs @@ -97,7 +97,7 @@ namespace Microsoft.Extensions.DependencyInjection Check.NotNull(type, nameof(type)); Check.NotNullOrWhiteSpace(remoteServiceConfigurationName, nameof(remoteServiceConfigurationName)); - AddHttpClientFactoryAndPolicy(services, remoteServiceConfigurationName); + AddHttpClientFactory(services, remoteServiceConfigurationName); services.Configure(options => { @@ -145,7 +145,7 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - private static IServiceCollection AddHttpClientFactoryAndPolicy( + private static IServiceCollection AddHttpClientFactory( [NotNull] this IServiceCollection services, [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName) { @@ -156,14 +156,23 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - var clientBuilder = services.AddHttpClient(remoteServiceConfigurationName); + var clientBuilder = services.AddHttpClient(remoteServiceConfigurationName, (provider, client) => + { + foreach (var clientBuildAction in preOptions.ProxyClientActions) + { + clientBuildAction(remoteServiceConfigurationName, provider, client); + } + }); foreach (var clientBuildAction in preOptions.ProxyClientBuildActions) { clientBuildAction(remoteServiceConfigurationName, clientBuilder); } - preOptions.ConfiguredProxyClients.Add(remoteServiceConfigurationName); + services.PreConfigure(options => + { + options.ConfiguredProxyClients.Add(remoteServiceConfigurationName); + }); return services; } diff --git a/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj b/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj index a96f3afd4a..e35ba45e59 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj +++ b/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs index 7f11a6be12..84ecc47014 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Microsoft.Extensions.DependencyInjection; namespace Volo.Abp.Http.Client @@ -10,10 +11,13 @@ namespace Volo.Abp.Http.Client internal HashSet ConfiguredProxyClients { get; } + public List> ProxyClientActions { get; } + public AbpHttpClientBuilderOptions() { ProxyClientBuildActions = new List>(); ConfiguredProxyClients = new HashSet(); + ProxyClientActions = new List>(); } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs index 37eefa8f5a..c4d0d0d181 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs @@ -1,11 +1,9 @@ -using System; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Castle; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; using Volo.Abp.Validation; -using Polly; using Volo.Abp.ExceptionHandling; namespace Volo.Abp.Http.Client @@ -20,22 +18,6 @@ namespace Volo.Abp.Http.Client )] public class AbpHttpClientModule : AbpModule { - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(options => - { - options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => - { - clientBuilder.AddTransientHttpErrorPolicy(policyBuilder => - policyBuilder.WaitAndRetryAsync( - 3, - i => TimeSpan.FromSeconds(Math.Pow(2, i)) - ) - ); - }); - }); - } - public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); diff --git a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj index eb5bc35428..0ed715a504 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj +++ b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj @@ -16,7 +16,7 @@ - + diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs index fae54ec08b..a4a08ce274 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs @@ -19,7 +19,12 @@ namespace Volo.Abp.Json Configure(options => { options.Providers.Add(); - options.Providers.Add(); + + var abpJsonOptions = context.Services.ExecutePreConfiguredActions(); + if (abpJsonOptions.UseHybridSerializer) + { + options.Providers.Add(); + } }); Configure(options => diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonOptions.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonOptions.cs index 55a4683e26..04c100e546 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonOptions.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonOptions.cs @@ -1,4 +1,5 @@ using Volo.Abp.Collections; +using Volo.Abp.Json.SystemTextJson; namespace Volo.Abp.Json { @@ -9,11 +10,19 @@ namespace Volo.Abp.Json /// public string DefaultDateTimeFormat { get; set; } + /// + /// It will try to use System.Json.Text to handle JSON if it can otherwise use Newtonsoft. + /// Affects both AbpJsonModule and AbpAspNetCoreMvcModule. + /// See + /// + public bool UseHybridSerializer { get; set; } + public ITypeList Providers { get; } public AbpJsonOptions() { Providers = new TypeList(); + UseHybridSerializer = true; } } } diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs index 4b56997d47..9702d4078f 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs @@ -1,10 +1,9 @@ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Json.SystemTextJson.JsonConverters; -namespace Volo.Abp.Json +namespace Volo.Abp.Json.SystemTextJson { public class AbpSystemTextJsonSerializerOptionsSetup : IConfigureOptions { @@ -19,6 +18,9 @@ namespace Volo.Abp.Json { options.JsonSerializerOptions.Converters.Add(ServiceProvider.GetRequiredService()); options.JsonSerializerOptions.Converters.Add(ServiceProvider.GetRequiredService()); + + options.JsonSerializerOptions.Converters.Add(new AbpStringToEnumConverter()); + options.JsonSerializerOptions.Converters.Add(new AbpStringToBooleanConverter()); } } } diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToBooleanConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToBooleanConverter.cs new file mode 100644 index 0000000000..dcfe08f5c3 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToBooleanConverter.cs @@ -0,0 +1,38 @@ +using System; +using System.Buffers; +using System.Buffers.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Volo.Abp.Json.SystemTextJson.JsonConverters +{ + public class AbpStringToBooleanConverter : JsonConverter + { + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (Utf8Parser.TryParse(span, out bool b1, out var bytesConsumed) && span.Length == bytesConsumed) + { + return b1; + } + + if (bool.TryParse(reader.GetString(), out var b2)) + { + return b2; + } + } + + return reader.GetBoolean(); + } + + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) + { + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.Remove(this); + var entityConverter = (JsonConverter)newOptions.GetConverter(typeof(bool)); + entityConverter.Write(writer, value, newOptions); + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToEnumConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToEnumConverter.cs new file mode 100644 index 0000000000..0de49a76b2 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToEnumConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Volo.Abp.Json.SystemTextJson.JsonConverters +{ + public class AbpStringToEnumConverter : JsonConverter + { + private readonly JsonStringEnumConverter _innerJsonStringEnumConverter; + + public AbpStringToEnumConverter() + : this(namingPolicy: null, allowIntegerValues: true) + { + } + + public AbpStringToEnumConverter(JsonNamingPolicy namingPolicy = null, bool allowIntegerValues = true) + { + _innerJsonStringEnumConverter = new JsonStringEnumConverter(namingPolicy, allowIntegerValues); + } + + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsEnum; + } + + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.Remove(this); + newOptions.Converters.Add(_innerJsonStringEnumConverter.CreateConverter(typeToConvert, newOptions)); + return JsonSerializer.Deserialize(ref reader, typeToConvert, newOptions); + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.Remove(this); + JsonSerializer.Serialize(writer, value, newOptions); + } + } +} diff --git a/framework/src/Volo.Abp.Swashbuckle/Microsoft/AspNetCore/Builder/AbpSwaggerUIBuilderExtensions.cs b/framework/src/Volo.Abp.Swashbuckle/Microsoft/AspNetCore/Builder/AbpSwaggerUIBuilderExtensions.cs index 4ea501626b..4a54191459 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Microsoft/AspNetCore/Builder/AbpSwaggerUIBuilderExtensions.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Microsoft/AspNetCore/Builder/AbpSwaggerUIBuilderExtensions.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Extensions.DependencyInjection; using Swashbuckle.AspNetCore.SwaggerUI; -using Volo.Abp; using Volo.Abp.Swashbuckle; namespace Microsoft.AspNetCore.Builder diff --git a/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs new file mode 100644 index 0000000000..1da6958830 --- /dev/null +++ b/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class AbpSwaggerGenServiceCollectionExtensions + { + public static IServiceCollection AddAbpSwaggerGenWithOAuth( + this IServiceCollection services, + [NotNull] string authority, + [NotNull] Dictionary scopes, + Action setupAction = null) + { + return services.AddSwaggerGen( + options => + { + options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows + { + AuthorizationCode = new OpenApiOAuthFlow + { + AuthorizationUrl = new Uri($"{authority.EnsureEndsWith('/')}connect/authorize"), + Scopes = scopes, + TokenUrl = new Uri($"{authority.EnsureEndsWith('/')}connect/token") + } + } + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "oauth2" + } + }, + Array.Empty() + } + }); + + + setupAction?.Invoke(options); + }); + } + } +} diff --git a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js index b1b9614640..22277cb194 100644 --- a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js +++ b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js @@ -3,8 +3,7 @@ (function () { abp.SwaggerUIBundle = function (configObject) { configObject.requestInterceptor = function (request) { - var token = abp.auth.getToken(); - request.headers.Authorization = token ? "Bearer " + token : null; + var antiForgeryToken = abp.security.antiForgery.getToken(); if (antiForgeryToken) { request.headers[abp.security.antiForgery.tokenHeaderName] = antiForgeryToken; diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj b/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj index 748524acd2..688ba76982 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj b/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj index cbb90d7379..0b4bffe3eb 100644 --- a/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj +++ b/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj @@ -18,7 +18,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs index dc0ae30d71..9f8e431d51 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs @@ -47,5 +47,13 @@ namespace Volo.Abp.AspNetCore.Mvc.Response Response.Redirect("/"); await Task.CompletedTask; } + + [HttpGet] + [Route("TestAsyncMethodWithResultFilter")] + [TestResultFilter] + public async Task TestAsyncMethodWithResultFilter() + { + await Task.CompletedTask; + } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs index 0002a4801b..b11a185d68 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs @@ -10,49 +10,50 @@ namespace Volo.Abp.AspNetCore.Mvc.Response [Fact] public async Task Should_Set_No_Content_For_Void_Action() { - var result = await GetResponseAsync("/api/NoContent-Test/TestMethod", HttpStatusCode.NoContent) - ; + var result = await GetResponseAsync("/api/NoContent-Test/TestMethod", HttpStatusCode.NoContent); result.StatusCode.ShouldBe(HttpStatusCode.NoContent); } [Fact] public async Task Should_Not_Set_No_Content_For_Not_Void_Action() { - var result = await GetResponseAsync("/api/NoContent-Test/TestMethodWithReturn") - ; + var result = await GetResponseAsync("/api/NoContent-Test/TestMethodWithReturn"); result.StatusCode.ShouldBe(HttpStatusCode.OK); } [Fact] public async Task Should_Not_Set_No_Content_For_Custom_Http_Status_Code_Action() { - var result = await GetResponseAsync("/api/NoContent-Test/TestCustomHttpStatusCodeMethod", HttpStatusCode.Redirect) - ; + var result = await GetResponseAsync("/api/NoContent-Test/TestCustomHttpStatusCodeMethod", HttpStatusCode.Redirect); result.StatusCode.ShouldBe(HttpStatusCode.Redirect); } [Fact] public async Task Should_Set_No_Content_For_Task_Action() { - var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethod", HttpStatusCode.NoContent) - ; + var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethod", HttpStatusCode.NoContent); result.StatusCode.ShouldBe(HttpStatusCode.NoContent); } [Fact] public async Task Should_Not_Set_No_Content_For_Not_Task_Action() { - var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethodWithReturn") - ; + var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethodWithReturn"); result.StatusCode.ShouldBe(HttpStatusCode.OK); } [Fact] public async Task Should_Not_Set_No_Content_For_Custom_Http_Status_Code_Async_Action() { - var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncCustomHttpStatusCodeMethod", HttpStatusCode.Redirect) - ; + var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncCustomHttpStatusCodeMethod", HttpStatusCode.Redirect); result.StatusCode.ShouldBe(HttpStatusCode.Redirect); } + + [Fact] + public async Task Should_Not_Set_No_Content_For_Custom_Result_Filter_Async_Action() + { + var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethodWithResultFilter"); + result.StatusCode.ShouldBe(HttpStatusCode.OK); + } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/TestResultFilter.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/TestResultFilter.cs new file mode 100644 index 0000000000..8cbbbc4399 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/TestResultFilter.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Mvc.Response +{ + public class TestResultFilter : Attribute, IAsyncActionFilter + { + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + await next(); + context.Result = new ObjectResult("TestResultFilter"); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj index f4f91b2629..e048ec5869 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj @@ -8,7 +8,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj index 606c1a5122..f3a7ddacf4 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj index f400d8e552..35ed7def1b 100644 --- a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj index cc51ad5e35..da6d0c0fa5 100644 --- a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj +++ b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/global.json b/global.json index f87af98b74..a46dc9b064 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "5.0.100-rc.2.20479.15", + "version": "5.0.100", "rollForward": "latestFeature" } } diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj index a15db40e48..f55d41a23e 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj @@ -21,7 +21,7 @@ - + diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj index fd7f8f2e3c..b499842ed5 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj +++ b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@ - + diff --git a/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/AccountManage.razor.cs b/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/AccountManage.razor.cs index a56da4b775..e647fd9549 100644 --- a/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/AccountManage.razor.cs +++ b/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/AccountManage.razor.cs @@ -43,7 +43,7 @@ namespace Volo.Abp.Account.Blazor.Pages.Account if (ChangePasswordModel.NewPassword != ChangePasswordModel.NewPasswordConfirm) { - await UiMessageService.WarnAsync(L["NewPasswordConfirmFailed"]); + await UiMessageService.Warn(L["NewPasswordConfirmFailed"]); return; } @@ -53,7 +53,7 @@ namespace Volo.Abp.Account.Blazor.Pages.Account NewPassword = ChangePasswordModel.NewPassword }); - await UiMessageService.SuccessAsync(L["PasswordChanged"]); + await UiMessageService.Success(L["PasswordChanged"]); } protected async Task UpdatePersonalInfoAsync() @@ -62,7 +62,7 @@ namespace Volo.Abp.Account.Blazor.Pages.Account ObjectMapper.Map(PersonalInfoModel) ); - await UiMessageService.SuccessAsync(L["PersonalSettingsSaved"]); + await UiMessageService.Success(L["PersonalSettingsSaved"]); } } diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index ec41402629..3f6167185f 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -38,7 +38,7 @@ - + diff --git a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index c2053ccaff..d00999c6b4 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -40,7 +40,7 @@ - + diff --git a/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj b/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj index b48d633d96..f0806249e6 100644 --- a/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj +++ b/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs index 03f61ca378..44ae98fcbf 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs @@ -171,16 +171,11 @@ namespace Volo.Abp.AuditLogging.MongoDB { var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); - var auditLogs = await query.As>() - .PageBy>(skipCount, maxResultCount) + var auditLogs = await query.As>() + .PageBy>(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); - // TODO: Improve this specification - - return auditLogs - .SelectMany(x => x.EntityChanges.Where(y => - IsSatisfiedEntityChange(y, auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName))) - .AsQueryable().OrderBy(sorting ?? "changeTime desc").ToList(); + return auditLogs.AsQueryable().OrderBy(sorting ?? "changeTime desc").ToList(); } public virtual async Task GetEntityChangeCountAsync( @@ -193,8 +188,8 @@ namespace Volo.Abp.AuditLogging.MongoDB CancellationToken cancellationToken = default) { var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); - var count = await query.As>() - .LongCountAsync(GetCancellationToken(cancellationToken)); + + var count = await query.As>().LongCountAsync(GetCancellationToken(cancellationToken)); return count; } @@ -228,7 +223,7 @@ namespace Volo.Abp.AuditLogging.MongoDB {EntityChange = x, UserName = auditLogs.First(y => y.Id == x.AuditLogId).UserName}).ToList(); } - protected virtual IQueryable GetEntityChangeListQuery( + protected virtual IQueryable GetEntityChangeListQuery( Guid? auditLogId = null, DateTime? startTime = null, DateTime? endTime = null, @@ -237,56 +232,14 @@ namespace Volo.Abp.AuditLogging.MongoDB string entityTypeFullName = null) { return GetMongoQueryable() - .Where(x => x.EntityChanges != null) + .SelectMany(x => x.EntityChanges) .WhereIf(auditLogId.HasValue, e => e.Id == auditLogId) - .WhereIf(startTime.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeTime >= startTime)) - .WhereIf(endTime.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeTime >= endTime)) - .WhereIf(changeType.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeType == changeType)) - .WhereIf(!string.IsNullOrWhiteSpace(entityId), e => e.EntityChanges.Any(ec => ec.EntityId == entityId)) + .WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime) + .WhereIf(endTime.HasValue, e => e.ChangeTime <= endTime) + .WhereIf(changeType.HasValue, e => e.ChangeType == changeType) + .WhereIf(!string.IsNullOrWhiteSpace(entityId), e => e.EntityId == entityId) .WhereIf(!string.IsNullOrWhiteSpace(entityTypeFullName), - e => e.EntityChanges.Any(ec => ec.EntityTypeFullName.Contains(entityTypeFullName))); - } - - protected virtual bool IsSatisfiedEntityChange( - EntityChange entityChange, - Guid? auditLogId = null, - DateTime? startTime = null, - DateTime? endTime = null, - EntityChangeType? changeType = null, - string entityId = null, - string entityTypeFullName = null) - { - if (auditLogId != null && auditLogId != entityChange.AuditLogId) - { - return false; - } - - if (startTime != null && startTime.Value >= entityChange.ChangeTime) - { - return false; - } - - if (endTime != null && endTime.Value <= entityChange.ChangeTime) - { - return false; - } - - if (changeType != null && changeType != entityChange.ChangeType) - { - return false; - } - - if (entityId != null && entityId != entityChange.EntityId) - { - return false; - } - - if (entityTypeFullName != null && entityChange.EntityTypeFullName.Contains(entityTypeFullName)) - { - return false; - } - - return true; + e => e.EntityTypeFullName.Contains(entityTypeFullName)); } } } diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj index d98c6f01e2..395670a1c7 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj index e01e0f89b5..9a24fa3b02 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj index 00ad8439b9..e106032e76 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj index 33d81803df..b874f6abf8 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj index 906bb08e7e..494e4639e1 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj @@ -20,13 +20,13 @@ - + - - + + diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj index 42a2defd23..2c9388b46f 100644 --- a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj index c20efb9ba9..ef06f64902 100644 --- a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj b/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj index d56298d39d..7004bb354e 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj @@ -24,7 +24,7 @@ - + diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj index 83ba117670..4be1a7f23b 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj @@ -21,7 +21,7 @@ - + diff --git a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj index 20341c2df5..7f05471351 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj @@ -24,7 +24,7 @@ - + diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj index a81b088c54..95c8963f1f 100644 --- a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj @@ -14,9 +14,9 @@ - - - + + + diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj index c62935e032..c8bffec85e 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/Volo.CmsKit.Web.Host.csproj b/modules/cms-kit/host/Volo.CmsKit.Web.Host/Volo.CmsKit.Web.Host.csproj index bb7592e7b4..955cf491ed 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/Volo.CmsKit.Web.Host.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/Volo.CmsKit.Web.Host.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Volo.CmsKit.Web.Unified.csproj b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Volo.CmsKit.Web.Unified.csproj index c7a507d2b5..ba613daf88 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Volo.CmsKit.Web.Unified.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Volo.CmsKit.Web.Unified.csproj @@ -13,7 +13,7 @@ - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj index 94641fbfb1..b965829d85 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj @@ -18,7 +18,7 @@ - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj index 80b92209a8..7ef1822195 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj @@ -19,7 +19,7 @@ - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo.CmsKit.Domain.Shared.csproj b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo.CmsKit.Domain.Shared.csproj index 84744e79b7..a00ed98617 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo.CmsKit.Domain.Shared.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo.CmsKit.Domain.Shared.csproj @@ -12,7 +12,7 @@ - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj index 57e6ec872e..7f945a232e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Web/Volo.CmsKit.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Web/Volo.CmsKit.Web.csproj index e7dd506f08..1a026b2521 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Web/Volo.CmsKit.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Web/Volo.CmsKit.Web.csproj @@ -19,7 +19,7 @@ - + diff --git a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/Volo.CmsKit.EntityFrameworkCore.Tests.csproj b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/Volo.CmsKit.EntityFrameworkCore.Tests.csproj index 060278f8fa..cde9dffb97 100644 --- a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/Volo.CmsKit.EntityFrameworkCore.Tests.csproj +++ b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/Volo.CmsKit.EntityFrameworkCore.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/modules/cms-kit/test/Volo.CmsKit.HttpApi.Client.ConsoleTestApp/Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj b/modules/cms-kit/test/Volo.CmsKit.HttpApi.Client.ConsoleTestApp/Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj index ffb0100ed0..3a41b650f6 100644 --- a/modules/cms-kit/test/Volo.CmsKit.HttpApi.Client.ConsoleTestApp/Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj +++ b/modules/cms-kit/test/Volo.CmsKit.HttpApi.Client.ConsoleTestApp/Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj @@ -20,7 +20,7 @@ - + diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj b/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj index 8f648b3dec..861c087ff0 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj index cd357fc46a..e4420cb87e 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj @@ -22,7 +22,7 @@ - + diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj index d5b8f49f19..f6a6175784 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj +++ b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj @@ -27,7 +27,7 @@ - + diff --git a/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj b/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj index 5fc5cd77f6..ae0a4f5842 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj +++ b/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj @@ -17,10 +17,10 @@ - + - + diff --git a/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj b/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj index 540866dbd1..b67bf5850e 100644 --- a/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj +++ b/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj @@ -24,7 +24,7 @@ - + diff --git a/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo.Docs.EntityFrameworkCore.Tests.csproj b/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo.Docs.EntityFrameworkCore.Tests.csproj index b979007f84..0af70ae609 100644 --- a/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo.Docs.EntityFrameworkCore.Tests.csproj +++ b/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo.Docs.EntityFrameworkCore.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs index a8f36ea20d..ea07a96b46 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs @@ -108,7 +108,7 @@ namespace Volo.Abp.FeatureManagement.Blazor.Components } else { - await UiMessageService.WarnAsync(L["Volo.Abp.FeatureManagement:InvalidFeatureValue", feature.DisplayName]); + await UiMessageService.Warn(L["Volo.Abp.FeatureManagement:InvalidFeatureValue", feature.DisplayName]); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj index 72513557ec..5fb53bf5e0 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Volo.Abp.FeatureManagement.Web.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Volo.Abp.FeatureManagement.Web.csproj index 36bc6cd239..e0bdc30dcf 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Volo.Abp.FeatureManagement.Web.csproj +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Volo.Abp.FeatureManagement.Web.csproj @@ -24,7 +24,7 @@ - + diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests.csproj b/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests.csproj index 9ad9ea8c9c..ddb292253b 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests.csproj +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj index ba5b69720d..3187b9946e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj @@ -27,7 +27,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index c1b4d42a04..fc39751980 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -39,13 +39,13 @@ "Volo.Abp.Identity:ConcurrencyFailure": "Optimistic concurrency failure, object has been modified.", "Volo.Abp.Identity:DuplicateEmail": "Email '{0}' is already taken.", "Volo.Abp.Identity:DuplicateRoleName": "Role name '{0}' is already taken.", - "Volo.Abp.Identity:DuplicateUserName": "User name '{0}' is already taken.", + "Volo.Abp.Identity:DuplicateUserName": "Username '{0}' is already taken.", "Volo.Abp.Identity:InvalidEmail": "Email '{0}' is invalid.", "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "The provided PasswordHasherCompatibilityMode is invalid.", "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "The iteration count must be a positive integer.", "Volo.Abp.Identity:InvalidRoleName": "Role name '{0}' is invalid.", "Volo.Abp.Identity:InvalidToken": "Invalid token.", - "Volo.Abp.Identity:InvalidUserName": "User name '{0}' is invalid, can only contain letters or digits.", + "Volo.Abp.Identity:InvalidUserName": "Username '{0}' is invalid, can only contain letters or digits.", "Volo.Abp.Identity:LoginAlreadyAssociated": "A user with this login already exists.", "Volo.Abp.Identity:PasswordMismatch": "Incorrect password.", "Volo.Abp.Identity:PasswordRequiresDigit": "Passwords must have at least one digit ('0'-'9').", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj index 670f686cfd..c8f9990468 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj @@ -30,7 +30,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj index 690d9ec6f4..16b42720a8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -43,7 +43,7 @@ - + diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj index dcdf00e451..a81a17bd57 100644 --- a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo.Abp.PermissionManagement.Domain.Shared.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo.Abp.PermissionManagement.Domain.Shared.csproj index 68f1166326..6689b76fb9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo.Abp.PermissionManagement.Domain.Shared.csproj +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo.Abp.PermissionManagement.Domain.Shared.csproj @@ -25,7 +25,7 @@ - + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj index 44ed5e9f88..588d1312bc 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj @@ -32,7 +32,7 @@ - + diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo.Abp.PermissionManagement.Domain.Tests.csproj b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo.Abp.PermissionManagement.Domain.Tests.csproj index 692cda97e9..afd6ec8d93 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo.Abp.PermissionManagement.Domain.Tests.csproj +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo.Abp.PermissionManagement.Domain.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj index fffc43852c..e09af1b9d2 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo.Abp.SettingManagement.Domain.Shared.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo.Abp.SettingManagement.Domain.Shared.csproj index 3b1c037a4b..b3040f828c 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo.Abp.SettingManagement.Domain.Shared.csproj +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo.Abp.SettingManagement.Domain.Shared.csproj @@ -21,7 +21,7 @@ - + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj index b841112085..130539b07a 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj @@ -21,7 +21,7 @@ - + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj index be0a123294..22b8a95bec 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj @@ -25,7 +25,7 @@ - + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj index ab702e874d..bc88833315 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj @@ -35,7 +35,7 @@ - + diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj b/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj index 9443cce6c9..2bf8020eb9 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index 42284c0e07..aa37107242 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -61,7 +61,6 @@ "@ngxs/devtools-plugin": "^3.7.0", "@ngxs/logger-plugin": "^3.7.0", "@ngxs/router-plugin": "^3.7.0", - "@ngxs/storage-plugin": "^3.7.0", "@ngxs/store": "^3.7.0", "@schematics/angular": "~10.0.5", "@swimlane/ngx-datatable": "^17.1.0", diff --git a/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts b/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts index 3a6a8663cc..4a9318bf21 100644 --- a/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts @@ -1,4 +1,4 @@ -import { AuthService, SetRemember, ConfigState } from '@abp/ng.core'; +import { AuthService, ConfigState } from '@abp/ng.core'; import { ToasterService } from '@abp/ng.theme.shared'; import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -65,8 +65,6 @@ export class LoginComponent implements OnInit { }), finalize(() => (this.inProgress = false)), ) - .subscribe(() => { - this.store.dispatch(new SetRemember(this.form.get('remember').value)); - }); + .subscribe(() => {}); } } diff --git a/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts b/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts index 92d35fcf2e..4fcb77f96c 100644 --- a/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts @@ -1,4 +1,4 @@ -import { ABP, GetAppConfiguration, SessionState, SetTenant } from '@abp/ng.core'; +import { ABP, GetAppConfiguration, SessionStateService } from '@abp/ng.core'; import { ToasterService } from '@abp/ng.theme.shared'; import { Component } from '@angular/core'; import { Select, Store } from '@ngxs/store'; @@ -13,8 +13,7 @@ import { AccountService } from '../../services/account.service'; }) export class TenantBoxComponent implements Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs { - @Select(SessionState.getTenant) - currentTenant$: Observable; + currentTenant$ = this.sessionState.getTenant$(); name: string; @@ -26,11 +25,12 @@ export class TenantBoxComponent private store: Store, private toasterService: ToasterService, private accountService: AccountService, + private sessionState: SessionStateService, ) {} onSwitch() { - const tenant = this.store.selectSnapshot(SessionState.getTenant); - this.name = (tenant || ({} as ABP.BasicItem)).name; + const tenant = this.sessionState.getTenant; + this.name = tenant?.name; this.isModalVisible = true; } @@ -57,7 +57,8 @@ export class TenantBoxComponent } private setTenant(tenant: ABP.BasicItem) { - return this.store.dispatch([new SetTenant(tenant), new GetAppConfiguration()]); + this.sessionState.setTenant(tenant); + return this.store.dispatch(new GetAppConfiguration()); } private showError() { diff --git a/npm/ng-packs/packages/core/ng-package.json b/npm/ng-packs/packages/core/ng-package.json index 045f0e8844..7236b0ca66 100644 --- a/npm/ng-packs/packages/core/ng-package.json +++ b/npm/ng-packs/packages/core/ng-package.json @@ -9,7 +9,6 @@ "@abp/utils", "@angular/localize", "@ngxs/router-plugin", - "@ngxs/storage-plugin", "@ngxs/store", "angular-oauth2-oidc", "just-compare", diff --git a/npm/ng-packs/packages/core/package.json b/npm/ng-packs/packages/core/package.json index 3c2a3381ac..d05476aba3 100644 --- a/npm/ng-packs/packages/core/package.json +++ b/npm/ng-packs/packages/core/package.json @@ -10,7 +10,6 @@ "@abp/utils": "^3.3.1", "@angular/localize": "~10.0.10", "@ngxs/router-plugin": "^3.7.0", - "@ngxs/storage-plugin": "^3.7.0", "@ngxs/store": "^3.7.0", "angular-oauth2-oidc": "^10.0.0", "just-clone": "^3.1.0", diff --git a/npm/ng-packs/packages/core/src/lib/actions/index.ts b/npm/ng-packs/packages/core/src/lib/actions/index.ts index 9ef0b1278a..68436a5b0d 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/index.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/index.ts @@ -3,4 +3,3 @@ export * from './loader.actions'; export * from './profile.actions'; export * from './replaceable-components.actions'; export * from './rest.actions'; -export * from './session.actions'; diff --git a/npm/ng-packs/packages/core/src/lib/actions/session.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/session.actions.ts deleted file mode 100644 index a7d2cd9f3c..0000000000 --- a/npm/ng-packs/packages/core/src/lib/actions/session.actions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ABP } from '../models'; - -export class SetLanguage { - static readonly type = '[Session] Set Language'; - constructor(public payload: string, public dispatchAppConfiguration?: boolean) {} -} -export class SetTenant { - static readonly type = '[Session] Set Tenant'; - constructor(public payload: ABP.BasicItem) {} -} -export class ModifyOpenedTabCount { - static readonly type = '[Session] Modify Opened Tab Count'; - constructor(public operation: 'increase' | 'decrease') {} -} -export class SetRemember { - static readonly type = '[Session] Set Remember'; - constructor(public payload: boolean) {} -} diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 3d6870f577..387437cc04 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -74,7 +74,7 @@ export class DynamicLayoutComponent { } private listenToLanguageChange() { - this.subscription.addOne(this.localizationService.languageChange, () => { + this.subscription.addOne(this.localizationService.languageChange$, () => { this.isLayoutVisible = false; setTimeout(() => (this.isLayoutVisible = true), 0); }); diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index 3756bff1bb..6e1e9147b3 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -4,11 +4,6 @@ import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angul import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { NgxsRouterPluginModule } from '@ngxs/router-plugin'; -import { - NgxsStoragePluginModule, - NGXS_STORAGE_PLUGIN_OPTIONS, - StorageOption, -} from '@ngxs/storage-plugin'; import { NgxsModule, NGXS_PLUGINS } from '@ngxs/store'; import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc'; import { AbstractNgModelComponent } from './abstracts/ng-model.component'; @@ -38,7 +33,6 @@ import { LocalizationService } from './services/localization.service'; import { ConfigState } from './states/config.state'; import { ProfileState } from './states/profile.state'; import { ReplaceableComponentsState } from './states/replaceable-components.state'; -import { SessionState } from './states/session.state'; import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token'; import { noop } from './utils/common-utils'; import './utils/date-extensions'; @@ -121,9 +115,8 @@ export class BaseCoreModule {} imports: [ BaseCoreModule, LocalizationModule, - NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, SessionState, ConfigState]), + NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, ConfigState]), NgxsRouterPluginModule.forRoot(), - NgxsStoragePluginModule.forRoot(), OAuthModule.forRoot(), HttpClientXsrfModule.withOptions({ cookieName: 'XSRF-TOKEN', @@ -225,18 +218,6 @@ export class CoreModule { useFactory: noop, }, { provide: OAuthStorage, useFactory: storageFactory }, - { - provide: NGXS_STORAGE_PLUGIN_OPTIONS, - useValue: { - storage: StorageOption.LocalStorage, - serialize: JSON.stringify, - deserialize: JSON.parse, - beforeSerialize: ngxsStoragePluginSerialize, - afterDeserialize: ngxsStoragePluginSerialize, - ...options.ngxsStoragePluginOptions, - key: [...(options.ngxsStoragePluginOptions?.key || []), 'SessionState'], - }, - }, ], }; } diff --git a/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts b/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts index b27c7fa80a..79ec96809c 100644 --- a/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts +++ b/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts @@ -1,16 +1,20 @@ +import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders } from '@angular/common/http'; -import { OAuthService } from 'angular-oauth2-oidc'; import { Store } from '@ngxs/store'; -import { SessionState } from '../states'; -import { StartLoader, StopLoader } from '../actions/loader.actions'; +import { OAuthService } from 'angular-oauth2-oidc'; import { finalize } from 'rxjs/operators'; +import { StartLoader, StopLoader } from '../actions/loader.actions'; +import { SessionStateService } from '../services/session-state.service'; @Injectable({ providedIn: 'root', }) export class ApiInterceptor implements HttpInterceptor { - constructor(private oAuthService: OAuthService, private store: Store) {} + constructor( + private oAuthService: OAuthService, + private store: Store, + private sessionState: SessionStateService, + ) {} intercept(request: HttpRequest, next: HttpHandler) { this.store.dispatch(new StartLoader(request)); @@ -32,12 +36,12 @@ export class ApiInterceptor implements HttpInterceptor { headers['Authorization'] = `Bearer ${token}`; } - const lang = this.store.selectSnapshot(SessionState.getLanguage); + const lang = this.sessionState.getLanguage(); if (!existingHeaders?.has('Accept-Language') && lang) { headers['Accept-Language'] = lang; } - const tenant = this.store.selectSnapshot(SessionState.getTenant); + const tenant = this.sessionState.getTenant(); if (!existingHeaders?.has('__tenant') && tenant) { headers['__tenant'] = tenant.id; } diff --git a/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts b/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts index c974f842a2..0e10b12752 100644 --- a/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts +++ b/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts @@ -6,6 +6,7 @@ export namespace ApplicationConfiguration { auth: Auth; setting: Value; currentUser: CurrentUser; + currentTenant: CurrentTenant; features: Value; } @@ -74,4 +75,10 @@ export namespace ApplicationConfiguration { phoneNumberVerified: boolean; surName: string; } + + export interface CurrentTenant { + id: string; + name: string; + isAvailable?: boolean; + } } diff --git a/npm/ng-packs/packages/core/src/lib/models/common.ts b/npm/ng-packs/packages/core/src/lib/models/common.ts index 8a4e283bd9..6e4eead683 100644 --- a/npm/ng-packs/packages/core/src/lib/models/common.ts +++ b/npm/ng-packs/packages/core/src/lib/models/common.ts @@ -1,6 +1,5 @@ import { EventEmitter, Type } from '@angular/core'; import { Router } from '@angular/router'; -import { NgxsStoragePluginOptions } from '@ngxs/storage-plugin'; import { Subject } from 'rxjs'; import { eLayoutType } from '../enums/common'; import { Config } from './config'; @@ -11,7 +10,6 @@ export namespace ABP { registerLocaleFn: (locale: string) => Promise; skipGetAppConfiguration?: boolean; sendNullsAsQueryParam?: boolean; - ngxsStoragePluginOptions?: NgxsStoragePluginOptions & { key?: string[] }; } export interface Test { diff --git a/npm/ng-packs/packages/core/src/lib/models/session.ts b/npm/ng-packs/packages/core/src/lib/models/session.ts index 110fad53f5..2680ea51d9 100644 --- a/npm/ng-packs/packages/core/src/lib/models/session.ts +++ b/npm/ng-packs/packages/core/src/lib/models/session.ts @@ -1,9 +1,9 @@ -import { ABP } from '../models'; +import { ApplicationConfiguration } from './application-configuration'; export namespace Session { export interface State { language: string; - tenant: ABP.BasicItem; + tenant: ApplicationConfiguration.CurrentTenant; sessionDetail: SessionDetail; } diff --git a/npm/ng-packs/packages/core/src/lib/services/auth.service.ts b/npm/ng-packs/packages/core/src/lib/services/auth.service.ts index 94a48c00a4..d694691701 100644 --- a/npm/ng-packs/packages/core/src/lib/services/auth.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/auth.service.ts @@ -8,9 +8,9 @@ import { switchMap, take, tap } from 'rxjs/operators'; import snq from 'snq'; import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; import { ConfigState } from '../states/config.state'; -import { SessionState } from '../states/session.state'; import { AuthFlowStrategy, AUTH_FLOW_STRATEGY } from '../strategies/auth-flow.strategy'; import { RestService } from './rest.service'; +import { SessionStateService } from './session-state.service'; @Injectable({ providedIn: 'root', @@ -29,6 +29,7 @@ export class AuthService { private rest: RestService, private oAuthService: OAuthService, private store: Store, + private sessionState: SessionStateService, @Optional() @Inject('ACCOUNT_OPTIONS') private options: any, ) { this.setStrategy(); @@ -55,7 +56,7 @@ export class AuthService { } login(username: string, password: string): Observable { - const tenant = this.store.selectSnapshot(SessionState.getTenant); + const tenant = this.sessionState.getTenant(); return from( this.oAuthService.fetchTokenUsingPasswordFlow( diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index 6fdce35d07..96f9dd5ce8 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -1,36 +1,37 @@ import { registerLocaleData } from '@angular/common'; import { Injectable, Injector, NgZone, Optional, SkipSelf } from '@angular/core'; import { ActivatedRouteSnapshot, Router } from '@angular/router'; -import { Actions, ofActionSuccessful, Store } from '@ngxs/store'; -import { noop, Observable } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; -import { SetLanguage } from '../actions/session.actions'; +import { Store } from '@ngxs/store'; +import { noop, Observable, of, Subject } from 'rxjs'; +import { filter, map, mapTo, switchMap } from 'rxjs/operators'; +import { GetAppConfiguration } from '../actions/config.actions'; import { ABP } from '../models/common'; import { Config } from '../models/config'; import { ConfigState } from '../states/config.state'; import { CORE_OPTIONS } from '../tokens/options.token'; import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils'; +import { SessionStateService } from './session-state.service'; type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean; @Injectable({ providedIn: 'root' }) export class LocalizationService { + private latestLang = this.sessionState.getLanguage(); + private _languageChange$ = new Subject(); + /** * Returns currently selected language */ get currentLang(): string { - return this.store.selectSnapshot(state => state.SessionState.language); + return this.latestLang; } - get languageChange(): Observable { - return this.actions.pipe( - ofActionSuccessful(SetLanguage), - filter((action: SetLanguage) => action.dispatchAppConfiguration !== false), - ); + get languageChange$(): Observable { + return this._languageChange$.asObservable(); } constructor( - private actions: Actions, + private sessionState: SessionStateService, private store: Store, private injector: Injector, private ngZone: NgZone, @@ -44,7 +45,21 @@ export class LocalizationService { } private listenToSetLanguage() { - this.languageChange.subscribe(({ payload }) => this.registerLocale(payload)); + this.sessionState + .onLanguageChange$() + .pipe( + filter( + lang => + this.store.selectSnapshot( + ConfigState.getDeep('localization.currentCulture.cultureName'), + ) !== lang, + ), + switchMap(lang => this.store.dispatch(new GetAppConfiguration()).pipe(mapTo(lang))), + ) + .subscribe(lang => { + this.registerLocale(lang); + this._languageChange$.next(lang); + }); } registerLocale(locale: string) { @@ -57,6 +72,7 @@ export class LocalizationService { return registerLocaleFn(locale).then(module => { if (module?.default) registerLocaleData(module.default); + this.latestLang = locale; this.ngZone.run(async () => { await router.navigateByUrl(router.url).catch(noop); diff --git a/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts b/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts index 3a66a99311..39505b45d5 100644 --- a/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts @@ -1,10 +1,9 @@ import { Injectable } from '@angular/core'; -import { Store } from '@ngxs/store'; import { Observable } from 'rxjs'; -import { SetTenant } from '../actions/session.actions'; import { ABP } from '../models/common'; import { FindTenantResultDto } from '../models/find-tenant-result-dto'; import { RestService } from './rest.service'; +import { SessionStateService } from './session-state.service'; @Injectable({ providedIn: 'root' }) export class MultiTenancyService { @@ -12,7 +11,7 @@ export class MultiTenancyService { set domainTenant(value: ABP.BasicItem) { this._domainTenant = value; - this.store.dispatch(new SetTenant(value)); + this.sessionState.setTenant(value); } get domainTenant() { @@ -23,7 +22,7 @@ export class MultiTenancyService { apiName = 'abp'; - constructor(private restService: RestService, private store: Store) {} + constructor(private restService: RestService, private sessionState: SessionStateService) {} findTenantByName(name: string, headers: ABP.Dictionary): Observable { return this.restService.request( diff --git a/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts index 2875e02824..9d2cc24ed2 100644 --- a/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts @@ -1,44 +1,71 @@ import { Injectable } from '@angular/core'; -import { Store } from '@ngxs/store'; -import { - SetLanguage, - SetRemember, - SetTenant, - ModifyOpenedTabCount, -} from '../actions/session.actions'; -import { SessionState } from '../states'; +import { ApplicationConfiguration } from '../models/application-configuration'; +import { Session } from '../models/session'; +import { InternalStore } from '../utils/internal-store-utils'; +import compare from 'just-compare'; + +export interface SessionDetail { + openedTabCount: number; + lastExitTime: number; + remember: boolean; +} @Injectable({ providedIn: 'root', }) export class SessionStateService { - constructor(private store: Store) {} + private readonly store = new InternalStore({} as Session.State); - getLanguage() { - return this.store.selectSnapshot(SessionState.getLanguage); + private updateLocalStorage = () => { + localStorage.setItem('abpSession', JSON.stringify(this.store.state)); + }; + + constructor() { + this.init(); } - getTenant() { - return this.store.selectSnapshot(SessionState.getTenant); + private init() { + const session = localStorage.getItem('abpSession'); + if (session) { + this.store.patch(JSON.parse(session)); + } + + this.store.sliceUpdate(state => state).subscribe(this.updateLocalStorage); + } + + onLanguageChange$() { + return this.store.sliceUpdate(state => state.language); } - getSessionDetail() { - return this.store.selectSnapshot(SessionState.getSessionDetail); + onTenantChange$() { + return this.store.sliceUpdate(state => state.tenant); } - dispatchSetLanguage(...args: ConstructorParameters) { - return this.store.dispatch(new SetLanguage(...args)); + getLanguage() { + return this.store.state.language; + } + + getLanguage$() { + return this.store.sliceState(state => state.language); + } + + getTenant() { + return this.store.state.tenant; } - dispatchSetTenant(...args: ConstructorParameters) { - return this.store.dispatch(new SetTenant(...args)); + getTenant$() { + return this.store.sliceState(state => state.tenant); } - dispatchSetRemember(...args: ConstructorParameters) { - return this.store.dispatch(new SetRemember(...args)); + setTenant(tenant: ApplicationConfiguration.CurrentTenant) { + if (compare(tenant, this.store.state.tenant)) return; + + this.store.patch({ tenant }); } - dispatchModifyOpenedTabCount(...args: ConstructorParameters) { - return this.store.dispatch(new ModifyOpenedTabCount(...args)); + setLanguage(language: string) { + if (language === this.store.state.language) return; + + this.store.patch({ language }); } } diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index 736acb56a7..d11c7b44c9 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -6,11 +6,10 @@ import { catchError, switchMap, tap } from 'rxjs/operators'; import snq from 'snq'; import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; import { RestOccurError } from '../actions/rest.actions'; -import { SetLanguage } from '../actions/session.actions'; import { ApplicationConfiguration } from '../models/application-configuration'; import { Config } from '../models/config'; +import { SessionStateService } from '../services/session-state.service'; import { interpolate } from '../utils/string-utils'; -import { SessionState } from './session.state'; @State({ name: 'ConfigState', @@ -211,7 +210,11 @@ export class ConfigState { return selector; } - constructor(private http: HttpClient, private store: Store) {} + constructor( + private http: HttpClient, + private store: Store, + private sessionState: SessionStateService, + ) {} @Action(GetAppConfiguration) addData({ patchState, dispatch }: StateContext) { @@ -226,7 +229,7 @@ export class ConfigState { }), ), switchMap(configuration => { - if (this.store.selectSnapshot(SessionState.getLanguage)) return of(null); + if (this.sessionState.getLanguage()) return of(null); let lang = configuration.localization.currentCulture.cultureName; if (lang.includes(';')) { @@ -234,7 +237,7 @@ export class ConfigState { } document.documentElement.setAttribute('lang', lang); - return dispatch(new SetLanguage(lang, false)); + return of(null).pipe(tap(() => this.sessionState.setLanguage(lang))); }), catchError((err: HttpErrorResponse) => { dispatch(new RestOccurError(err)); diff --git a/npm/ng-packs/packages/core/src/lib/states/index.ts b/npm/ng-packs/packages/core/src/lib/states/index.ts index 7b48292caa..76a5510ac9 100644 --- a/npm/ng-packs/packages/core/src/lib/states/index.ts +++ b/npm/ng-packs/packages/core/src/lib/states/index.ts @@ -1,4 +1,3 @@ export * from './replaceable-components.state'; export * from './config.state'; export * from './profile.state'; -export * from './session.state'; diff --git a/npm/ng-packs/packages/core/src/lib/states/session.state.ts b/npm/ng-packs/packages/core/src/lib/states/session.state.ts deleted file mode 100644 index a9bb89d3f0..0000000000 --- a/npm/ng-packs/packages/core/src/lib/states/session.state.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Injectable } from '@angular/core'; -import { - Action, - Actions, - ofActionSuccessful, - Selector, - State, - StateContext, - Store, -} from '@ngxs/store'; -import { OAuthService } from 'angular-oauth2-oidc'; -import { fromEvent } from 'rxjs'; -import { take } from 'rxjs/operators'; -import { GetAppConfiguration } from '../actions/config.actions'; -import { - ModifyOpenedTabCount, - SetLanguage, - SetRemember, - SetTenant, -} from '../actions/session.actions'; -import { ABP, Session } from '../models'; - -@State({ - name: 'SessionState', - defaults: { sessionDetail: { openedTabCount: 0 } } as Session.State, -}) -@Injectable() -export class SessionState { - @Selector() - static getLanguage({ language }: Session.State): string { - return language; - } - - @Selector() - static getTenant({ tenant }: Session.State): ABP.BasicItem { - return tenant; - } - - @Selector() - static getSessionDetail({ sessionDetail }: Session.State): Session.SessionDetail { - return sessionDetail; - } - - constructor(private oAuthService: OAuthService, private store: Store, private actions: Actions) { - actions - .pipe(ofActionSuccessful(GetAppConfiguration)) - .pipe(take(1)) - .subscribe(() => { - const sessionDetail = this.store.selectSnapshot(SessionState)?.sessionDetail || {}; - - const fiveMinutesBefore = new Date().valueOf() - 5 * 60 * 1000; - - if ( - sessionDetail.lastExitTime && - sessionDetail.openedTabCount === 0 && - this.oAuthService.hasValidAccessToken() && - sessionDetail.remember === false && - sessionDetail.lastExitTime < fiveMinutesBefore - ) { - this.oAuthService.logOut(); - } - - this.store.dispatch(new ModifyOpenedTabCount('increase')); - - fromEvent(window, 'unload').subscribe(event => { - this.store.dispatch(new ModifyOpenedTabCount('decrease')); - }); - }); - } - - @Action(SetLanguage) - setLanguage( - { patchState, dispatch }: StateContext, - { payload, dispatchAppConfiguration = true }: SetLanguage, - ) { - patchState({ - language: payload, - }); - - if (dispatchAppConfiguration) return dispatch(new GetAppConfiguration()); - } - - @Action(SetTenant) - setTenant({ patchState }: StateContext, { payload }: SetTenant) { - patchState({ - tenant: payload, - }); - } - - @Action(SetRemember) - setRemember( - { getState, patchState }: StateContext, - { payload: remember }: SetRemember, - ) { - const { sessionDetail } = getState(); - - patchState({ - sessionDetail: { - ...sessionDetail, - remember, - }, - }); - } - - @Action(ModifyOpenedTabCount) - modifyOpenedTabCount( - { getState, patchState }: StateContext, - { operation }: ModifyOpenedTabCount, - ) { - // tslint:disable-next-line: prefer-const - let { openedTabCount, lastExitTime, ...detail } = - getState().sessionDetail || ({ openedTabCount: 0 } as Session.SessionDetail); - - if (operation === 'increase') { - openedTabCount++; - } else if (operation === 'decrease') { - openedTabCount--; - lastExitTime = new Date().valueOf(); - } - - if (!openedTabCount || openedTabCount < 0) { - openedTabCount = 0; - } - - patchState({ - sessionDetail: { - openedTabCount, - lastExitTime, - ...detail, - }, - }); - } -} diff --git a/npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts index 8285a6a9f4..ed42194755 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts @@ -4,30 +4,34 @@ import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { Subject, timer } from 'rxjs'; -import { ApiInterceptor } from '../interceptors'; import { StartLoader, StopLoader } from '../actions'; +import { ApiInterceptor } from '../interceptors'; +import { SessionStateService } from '../services'; describe('ApiInterceptor', () => { let spectator: SpectatorService; let interceptor: ApiInterceptor; let store: SpyObject; let oauthService: SpyObject; + let sessionState: SpyObject; const createService = createServiceFactory({ service: ApiInterceptor, - mocks: [OAuthService, Store], + mocks: [OAuthService, Store, SessionStateService], }); beforeEach(() => { spectator = createService(); interceptor = spectator.service; store = spectator.inject(Store); + sessionState = spectator.inject(SessionStateService); oauthService = spectator.inject(OAuthService); }); it('should add headers to http request', done => { oauthService.getAccessToken.andReturn('ey892mkwa8^2jk'); - store.selectSnapshot.andReturn({ id: 'test' }); + sessionState.getLanguage.andReturn('tr'); + sessionState.getTenant.andReturn({ id: 'Volosoft', name: 'Volosoft' }); const request = new HttpRequest('GET', 'https://abp.io'); const handleRes$ = new Subject(); @@ -35,8 +39,8 @@ describe('ApiInterceptor', () => { const handler = { handle: (req: HttpRequest) => { expect(req.headers.get('Authorization')).toEqual('Bearer ey892mkwa8^2jk'); - expect(req.headers.get('Accept-Language')).toEqual({ id: 'test' } as any); - expect(req.headers.get('__tenant')).toEqual('test'); + expect(req.headers.get('Accept-Language')).toEqual('tr'); + expect(req.headers.get('__tenant')).toEqual('Volosoft'); done(); return handleRes$; }, diff --git a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts index 1660f916c1..8a81dec2dd 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts @@ -2,10 +2,13 @@ import { HttpClient } from '@angular/common/http'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; import { Store } from '@ngxs/store'; import { of, ReplaySubject, timer } from 'rxjs'; -import { SetLanguage } from '../actions'; import { ApplicationConfiguration } from '../models/application-configuration'; import { Config } from '../models/config'; -import { ApplicationConfigurationService, ConfigStateService } from '../services'; +import { + ApplicationConfigurationService, + ConfigStateService, + SessionStateService, +} from '../services'; import { ConfigState } from '../states'; export const CONFIG_STATE_DATA = ({ @@ -116,7 +119,11 @@ describe('ConfigState', () => { spectator = createService(); store = spectator.inject(Store); service = spectator.service; - state = new ConfigState(spectator.inject(HttpClient), store); + state = new ConfigState( + spectator.inject(HttpClient), + store, + spectator.inject(SessionStateService), + ); }); describe('#getAll', () => { @@ -268,8 +275,6 @@ describe('ConfigState', () => { timer(0).subscribe(() => { expect(patchStateArg).toEqual(configuration); - expect(dispatchArg instanceof SetLanguage).toBeTruthy(); - expect(dispatchArg).toEqual({ payload: 'en', dispatchAppConfiguration: false }); done(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts index de1e27f756..628a685789 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts @@ -4,6 +4,7 @@ import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { of } from 'rxjs'; import { GetAppConfiguration } from '../actions'; +import { SessionStateService } from '../services'; import * as AuthFlowStrategy from '../strategies/auth-flow.strategy'; import { CORE_OPTIONS } from '../tokens/options.token'; import { checkAccessToken, getInitialData, localeInitializer } from '../utils'; @@ -79,9 +80,8 @@ describe('InitialUtils', () => { test('should resolve registerLocale', async () => { const injector = spectator.inject(Injector); const injectorSpy = jest.spyOn(injector, 'get'); - const store = spectator.inject(Store); - store.selectSnapshot.andCallFake(selector => selector({ SessionState: { language: 'tr' } })); - injectorSpy.mockReturnValueOnce(store); + const sessionState = spectator.inject(SessionStateService); + injectorSpy.mockReturnValueOnce(sessionState); injectorSpy.mockReturnValueOnce({ registerLocaleFn: () => Promise.resolve() }); expect(typeof localeInitializer(injector)).toBe('function'); expect(await localeInitializer(injector)()).toBe('resolved'); diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts index ca6b28acd7..3254ede573 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts @@ -2,12 +2,16 @@ import { CORE_OPTIONS } from '../tokens/options.token'; import { Router } from '@angular/router'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; import { Actions, Store } from '@ngxs/store'; -import { of, Subject } from 'rxjs'; +import { BehaviorSubject, of, Subject } from 'rxjs'; import { LocalizationService } from '../services/localization.service'; +import { SessionStateService } from '../services'; + +const shouldReuseRoute = () => true; describe('LocalizationService', () => { let spectator: SpectatorService; let store: SpyObject; + let router: SpyObject; let service: LocalizationService; const createService = createServiceFactory({ @@ -26,16 +30,21 @@ describe('LocalizationService', () => { beforeEach(() => { spectator = createService(); store = spectator.inject(Store); + store.dispatch.mockReturnValue(new BehaviorSubject('tr')); + router = spectator.inject(Router); + router.routeReuseStrategy = { shouldReuseRoute } as any; service = spectator.service; + + const sessionState = spectator.inject(SessionStateService); + sessionState.setLanguage('tr'); }); describe('#currentLang', () => { - it('should be tr', () => { - store.selectSnapshot.andCallFake((selector: (state: any, ...states: any[]) => string) => { - return selector({ SessionState: { language: 'tr' } }); - }); - - expect(service.currentLang).toBe('tr'); + it('should be tr', done => { + setTimeout(() => { + expect(service.currentLang).toBe('tr'); + done(); + }, 0); }); }); @@ -59,11 +68,6 @@ describe('LocalizationService', () => { describe('#registerLocale', () => { it('should return registerLocale and then call setRouteReuse', () => { - const router = spectator.inject(Router); - - const shouldReuseRoute = () => true; - router.routeReuseStrategy = { shouldReuseRoute } as any; - router.navigateByUrl.andCallFake(url => { return new Promise(resolve => resolve({ catch: () => null })); }); @@ -76,7 +80,13 @@ describe('LocalizationService', () => { it('should throw an error message when service have an otherInstance', async () => { try { - const instance = new LocalizationService(new Subject(), null, null, null, {} as any); + const instance = new LocalizationService( + { getLanguage: () => {} } as any, + null, + null, + null, + {} as any, + ); } catch (error) { expect((error as Error).message).toBe('LocalizationService should have only one instance.'); } diff --git a/npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts deleted file mode 100644 index d1c4dd5024..0000000000 --- a/npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; -import { SessionStateService } from '../services/session-state.service'; -import { SessionState } from '../states/session.state'; -import { Store } from '@ngxs/store'; -import * as SessionActions from '../actions'; -import { OAuthService } from 'angular-oauth2-oidc'; - -describe('SessionStateService', () => { - let service: SessionStateService; - let spectator: SpectatorService; - let store: SpyObject; - - const createService = createServiceFactory({ - service: SessionStateService, - mocks: [Store, OAuthService], - }); - beforeEach(() => { - spectator = createService(); - service = spectator.service; - store = spectator.inject(Store); - }); - test('should have the all SessionState static methods', () => { - const reg = /(?<=static )(.*)(?=\()/gm; - SessionState.toString() - .match(reg) - .forEach(fnName => { - expect(service[fnName]).toBeTruthy(); - - const spy = jest.spyOn(store, 'selectSnapshot'); - spy.mockClear(); - - const isDynamicSelector = SessionState[fnName].name !== 'memoized'; - - if (isDynamicSelector) { - SessionState[fnName] = jest.fn((...args) => args); - service[fnName]('test', 0, {}); - expect(SessionState[fnName]).toHaveBeenCalledWith('test', 0, {}); - } else { - service[fnName](); - expect(spy).toHaveBeenCalledWith(SessionState[fnName]); - } - }); - }); - - test('should have a dispatch method for every sessionState action', () => { - const reg = /(?<=dispatch)(\w+)(?=\()/gm; - SessionStateService.toString() - .match(reg) - .forEach(fnName => { - expect(SessionActions[fnName]).toBeTruthy(); - - const spy = jest.spyOn(store, 'dispatch'); - spy.mockClear(); - - const params = Array.from(new Array(SessionActions[fnName].length)); - - service[`dispatch${fnName}`](...params); - expect(spy).toHaveBeenCalledWith(new SessionActions[fnName](...params)); - }); - }); -}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts deleted file mode 100644 index 8a7953dd16..0000000000 --- a/npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { Session } from '../models/session'; -import { LocalizationService, AuthService } from '../services'; -import { SessionState } from '../states'; -import { GetAppConfiguration } from '../actions/config.actions'; -import { of, Subject } from 'rxjs'; -import { Store, Actions } from '@ngxs/store'; -import { OAuthService } from 'angular-oauth2-oidc'; - -export class DummyClass {} - -export const SESSION_STATE_DATA = { - language: 'tr', - tenant: { id: 'd5692aef-2ac6-49cd-9f3e-394c0bd4f8b3', name: 'Test' }, -} as Session.State; - -describe('SessionState', () => { - let spectator: SpectatorService; - let state: SessionState; - - const createService = createServiceFactory({ - service: DummyClass, - mocks: [LocalizationService, Store, Actions, OAuthService], - }); - - beforeEach(() => { - spectator = createService(); - state = new SessionState(null, null, new Subject()); - }); - - describe('#getLanguage', () => { - it('should return the current language', () => { - expect(SessionState.getLanguage(SESSION_STATE_DATA)).toEqual(SESSION_STATE_DATA.language); - }); - }); - - describe('#getTenant', () => { - it('should return the tenant object', () => { - expect(SessionState.getTenant(SESSION_STATE_DATA)).toEqual(SESSION_STATE_DATA.tenant); - }); - }); - - describe('#SetLanguage', () => { - it('should set the language and dispatch the GetAppConfiguration action', () => { - let patchedData; - let dispatchedData; - const patchState = jest.fn(data => (patchedData = data)); - const dispatch = jest.fn(action => { - dispatchedData = action; - return of({}); - }); - - state.setLanguage({ patchState, dispatch } as any, { payload: 'en' }).subscribe(); - - expect(patchedData).toEqual({ language: 'en' }); - expect(dispatchedData instanceof GetAppConfiguration).toBeTruthy(); - }); - }); - - describe('#setTenantId', () => { - it('should set the tenant', () => { - let patchedData; - const patchState = jest.fn(data => (patchedData = data)); - const testTenant = { id: '54ae02ba-9289-4c1b-8521-0ea437756288', name: 'Test Tenant' }; - state.setTenant({ patchState } as any, { payload: testTenant }); - - expect(patchedData).toEqual({ tenant: testTenant }); - }); - }); -}); diff --git a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts index cf7771c094..f34f46e6f0 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts @@ -4,8 +4,10 @@ import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { tap } from 'rxjs/operators'; import { GetAppConfiguration } from '../actions/config.actions'; +import { ApplicationConfiguration } from '../models/application-configuration'; import { ABP } from '../models/common'; import { AuthService } from '../services/auth.service'; +import { SessionStateService } from '../services/session-state.service'; import { ConfigState } from '../states/config.state'; import { clearOAuthStorage } from '../strategies/auth-flow.strategy'; import { CORE_OPTIONS } from '../tokens/options.token'; @@ -25,7 +27,17 @@ export function getInitialData(injector: Injector) { return store .dispatch(new GetAppConfiguration()) - .pipe(tap(res => checkAccessToken(store, injector))) + .pipe( + tap(() => checkAccessToken(store, injector)), + tap(() => { + const currentTenant = store.selectSnapshot( + ConfigState.getDeep('currentTenant'), + ) as ApplicationConfiguration.CurrentTenant; + if (!currentTenant?.id) return; + + injector.get(SessionStateService).setTenant(currentTenant); + }), + ) .toPromise(); }; @@ -41,10 +53,10 @@ export function checkAccessToken(store: Store, injector: Injector) { export function localeInitializer(injector: Injector) { const fn = () => { - const store: Store = injector.get(Store); + const sessionState = injector.get(SessionStateService); const { registerLocaleFn }: ABP.Root = injector.get(CORE_OPTIONS); - const lang = store.selectSnapshot(state => state.SessionState.language) || 'en'; + const lang = sessionState.getLanguage() || 'en'; return new Promise((resolve, reject) => { registerLocaleFn(lang).then(module => { diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts index 115e9e563e..fae3fa0cf4 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts @@ -1,6 +1,6 @@ -import { Component, OnInit, Input } from '@angular/core'; -import { Store, Select } from '@ngxs/store'; -import { SetLanguage, ConfigState, ApplicationConfiguration, SessionState } from '@abp/ng.core'; +import { ApplicationConfiguration, ConfigState, SessionStateService } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { Select } from '@ngxs/store'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import snq from 'snq'; @@ -75,14 +75,14 @@ export class LanguagesComponent implements OnInit { } get selectedLangCulture(): string { - return this.store.selectSnapshot(SessionState.getLanguage); + return this.sessionState.getLanguage(); } - constructor(private store: Store) {} + constructor(private sessionState: SessionStateService) {} ngOnInit() {} onChangeLang(cultureName: string) { - this.store.dispatch(new SetLanguage(cultureName)); + this.sessionState.setLanguage(cultureName); } } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html index 30d115216b..0f1e1ffac8 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html @@ -38,8 +38,27 @@ + {{ option.key }} + + + + + + + diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts index 4a28b14f22..6b5b241924 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts @@ -53,7 +53,7 @@ export class ExtensibleFormPropComponent implements OnChanges { } getComponent(prop: FormProp): string { - if (prop.options) return 'select'; + if (prop.options && prop.type !== ePropType.MultiSelect) return 'select'; switch (prop.type) { case ePropType.Boolean: @@ -66,6 +66,8 @@ export class ExtensibleFormPropComponent implements OnChanges { return 'textarea'; case ePropType.Time: return 'time'; + case ePropType.MultiSelect: + return 'multiselect'; default: return 'input'; } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/enums/props.enum.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/enums/props.enum.ts index 1acb15f8d4..a7c989569f 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/enums/props.enum.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/enums/props.enum.ts @@ -9,4 +9,5 @@ export const enum ePropType { String = 'string', Text = 'text', Time = 'time', + MultiSelect = 'multiselect', } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/enum.util.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/enum.util.ts index 869b9a8c80..6e9985d0f6 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/enum.util.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/enum.util.ts @@ -49,7 +49,7 @@ export function createEnumOptions( } function createLocalizationStream(l10n: LocalizationService, mapTarget: any) { - return merge(of(null), l10n.languageChange).pipe(map(() => mapTarget)); + return merge(of(null), l10n.languageChange$).pipe(map(() => mapTarget)); } function createEnumLocalizer( diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts index 68f7df2fb5..12f520b12a 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts @@ -1,10 +1,16 @@ import { LocalizationService } from '@abp/ng.core'; import { Store } from '@ngxs/store'; -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { take } from 'rxjs/operators'; import { PropData } from '../lib/models/props'; import { createEnum, createEnumOptions, createEnumValueResolver } from '../lib/utils/enum.util'; +const mockSessionState = { + languageChange$: new BehaviorSubject('tr'), + getLanguage: () => 'tr', + onLanguageChange$: () => new BehaviorSubject('tr'), +} as any; + const fields = [ { name: 'foo', value: 1 }, { name: 'bar', value: 2 }, @@ -38,7 +44,7 @@ describe('Enum Utils', () => { describe('#createEnumValueResolver', () => { const service = new LocalizationService( - new Subject().asObservable(), + mockSessionState, ({ selectSnapshot: () => ({ values: { @@ -78,9 +84,7 @@ describe('Enum Utils', () => { const propData = new MockPropData({ extraProperties: { EnumProp: value } }); propData.getInjected = () => service as any; - const resolved = await valueResolver(propData) - .pipe(take(1)) - .toPromise(); + const resolved = await valueResolver(propData).pipe(take(1)).toPromise(); expect(resolved).toBe(expected); }, @@ -89,7 +93,7 @@ describe('Enum Utils', () => { describe('#createEnumOptions', () => { const service = new LocalizationService( - new Subject().asObservable(), + mockSessionState, ({ selectSnapshot: () => ({ values: { @@ -119,9 +123,7 @@ describe('Enum Utils', () => { const propData = new MockPropData({}); propData.getInjected = () => service as any; - const resolved = await options(propData) - .pipe(take(1)) - .toPromise(); + const resolved = await options(propData).pipe(take(1)).toPromise(); expect(resolved).toEqual([ { key: 'Foo', value: 1 }, diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts index c52214bf95..79bed70563 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts @@ -57,14 +57,9 @@ export class LazyStyleHandler { const l10n = injector.get(LocalizationService); // will always listen, no need to unsubscribe - l10n.languageChange - .pipe( - map(({ payload }) => payload), - startWith(l10n.currentLang), - ) - .subscribe(locale => { - this.dir = getLocaleDirection(locale); - }); + l10n.languageChange$.pipe(startWith(l10n.currentLang)).subscribe(locale => { + this.dir = getLocaleDirection(locale); + }); } private setBodyDir(dir: LocaleDirection) { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts index 5836bee474..1c120db08f 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts @@ -4,7 +4,7 @@ import { EMPTY, of } from 'rxjs'; import { BOOTSTRAP } from '../constants/styles'; import { createLazyStyleHref, initLazyStyleHandler, LazyStyleHandler } from '../handlers'; -const languageChange = of({ payload: 'en' }); +const languageChange$ = of({ payload: 'en' }); describe('LazyStyleHandler', () => { let spectator: SpectatorService; @@ -16,7 +16,7 @@ describe('LazyStyleHandler', () => { providers: [ { provide: LocalizationService, - useValue: { currentLang: 'en', languageChange }, + useValue: { currentLang: 'en', languageChange$ }, }, ], }); @@ -53,7 +53,7 @@ describe('initLazyStyleHandler', () => { const generator = (function*() { yield undefined; // LAZY_STYLES yield { loaded: new Map() }; // LazyLoadService - yield { currentLang: 'en', languageChange: EMPTY }; // LocalizationService + yield { currentLang: 'en', languageChange$: EMPTY }; // LocalizationService })(); const injector = { diff --git a/npm/ng-packs/yarn.lock b/npm/ng-packs/yarn.lock index 98afa1cf3a..32320d2877 100644 --- a/npm/ng-packs/yarn.lock +++ b/npm/ng-packs/yarn.lock @@ -102,7 +102,7 @@ chart.js "^2.9.3" tslib "^2.0.0" -"@abp/utils@^3.3.0", "@abp/utils@^3.3.0-rc.2": +"@abp/utils@^3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.3.0.tgz#44ccacf4c415227e94981d907017de1c6f7225bb" integrity sha512-/JIcygVJu/Ob1G5IicL2XgOVjIIePC2t3QzgEbwaq6NDGMHiOm2quyS8Lj7TfjBeUFTJhCFEbXJMq0lGQ+KMww== diff --git a/npm/packs/vee-validate/abp.resourcemapping.js b/npm/packs/vee-validate/abp.resourcemapping.js new file mode 100644 index 0000000000..9dd99eb3cb --- /dev/null +++ b/npm/packs/vee-validate/abp.resourcemapping.js @@ -0,0 +1,5 @@ +module.exports = { + mappings: { + "@node_modules/vee-validate/dist/*.*": "@libs/vee-validate.js/" + } +} \ No newline at end of file diff --git a/npm/packs/vee-validate/package.json b/npm/packs/vee-validate/package.json new file mode 100644 index 0000000000..5f28d3f713 --- /dev/null +++ b/npm/packs/vee-validate/package.json @@ -0,0 +1,12 @@ +{ + "version": "3.3.1", + "name": "@abp/vee-validate.js", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@abp/vue.js": "~3.3.1", + "vee-validate.js": "^3.4.4" + }, + "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" +} diff --git a/npm/packs/vue/abp.resourcemapping.js b/npm/packs/vue/abp.resourcemapping.js new file mode 100644 index 0000000000..cd3029199d --- /dev/null +++ b/npm/packs/vue/abp.resourcemapping.js @@ -0,0 +1,5 @@ +module.exports = { + mappings: { + "@node_modules/vue/dist/*.*": "@libs/vue.js/" + } +} \ No newline at end of file diff --git a/npm/packs/vue/package.json b/npm/packs/vue/package.json new file mode 100644 index 0000000000..d9dad1a7d9 --- /dev/null +++ b/npm/packs/vue/package.json @@ -0,0 +1,11 @@ +{ + "version": "3.3.1", + "name": "@abp/vue.js", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "vue.js": "^3.0.2" + }, + "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" +} 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 1497390ba0..b292f22a0b 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 @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs index b983836985..dec205bdae 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs @@ -17,7 +17,6 @@ using Volo.Abp.UI.Navigation; using Volo.Abp.Identity.Blazor; using Volo.Abp.AutoMapper; using Volo.Abp.TenantManagement.Blazor; -using Volo.Abp.Http.Client; namespace MyCompanyName.MyProjectName.Blazor { @@ -30,11 +29,6 @@ namespace MyCompanyName.MyProjectName.Blazor )] public class MyProjectNameBlazorModule : AbpModule { - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigureProxyClient(context); - } - public override void ConfigureServices(ServiceConfigurationContext context) { var environment = context.Services.GetSingletonInstance(); @@ -107,14 +101,6 @@ namespace MyCompanyName.MyProjectName.Blazor }); } - private void PreConfigureProxyClient(ServiceConfigurationContext context) - { - PreConfigure(options => - { - options.ProxyClientBuildActions.Clear(); - }); - } - public override void OnApplicationInitialization(ApplicationInitializationContext context) { context.ServiceProvider diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj index 8c46ad5946..4d5b4c51f7 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj @@ -23,7 +23,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json index 0accabeaaa..2e4a69bd49 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json @@ -17,6 +17,11 @@ "MyProjectName_Blazor": { "ClientId": "MyProjectName_Blazor", "RootUrl": "https://localhost:44307" + }, + "MyProjectName_Swagger": { + "ClientId": "MyProjectName_Swagger", + "ClientSecret": "1q2w3e*", + "RootUrl": "https://localhost:44300" } } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj index 627a072cdc..ae9ce4c4d2 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj @@ -25,7 +25,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs index ebb027fa86..7397d2238f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs @@ -188,6 +188,22 @@ namespace MyCompanyName.MyProjectName.IdentityServer postLogoutRedirectUri: $"{blazorRootUrl}/authentication/logout-callback" ); } + + // Swagger Client + var swaggerClientId = configurationSection["MyProjectName_Swagger:ClientId"]; + if (!swaggerClientId.IsNullOrWhiteSpace()) + { + var swaggerRootUrl = configurationSection["MyProjectName_Swagger:RootUrl"].TrimEnd('/'); + + await CreateClientAsync( + name: swaggerClientId, + scopes: commonScopes, + grantTypes: new[] { "authorization_code" }, + secret: configurationSection["MyProjectName_Swagger:ClientSecret"]?.Sha256(), + requireClientSecret: false, + redirectUri: $"{swaggerRootUrl}/swagger/oauth2-redirect.html" + ); + } } private async Task CreateClientAsync( @@ -281,4 +297,4 @@ namespace MyCompanyName.MyProjectName.IdentityServer return await _clientRepository.UpdateAsync(client); } } -} \ No newline at end of file +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations.csproj index fedc72ecc2..5803aac5a0 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations.csproj @@ -12,7 +12,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index 26f3d6148a..2c2569a98c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -12,14 +12,14 @@ - - - - + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 1a6c410619..6a0ee29447 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -23,6 +24,7 @@ using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Swashbuckle; using Volo.Abp.VirtualFileSystem; namespace MyCompanyName.MyProjectName @@ -34,7 +36,8 @@ namespace MyCompanyName.MyProjectName typeof(AbpAspNetCoreMvcUiMultiTenancyModule), typeof(MyProjectNameApplicationModule), typeof(MyProjectNameEntityFrameworkCoreDbMigrationsModule), - typeof(AbpAspNetCoreSerilogModule) + typeof(AbpAspNetCoreSerilogModule), + typeof(AbpSwashbuckleModule) )] public class MyProjectNameHttpApiHostModule : AbpModule { @@ -52,7 +55,7 @@ namespace MyCompanyName.MyProjectName ConfigureVirtualFileSystem(context); ConfigureRedis(context, configuration, hostingEnvironment); ConfigureCors(context, configuration); - ConfigureSwaggerServices(context); + ConfigureSwaggerServices(context, configuration); } private void ConfigureCache(IConfiguration configuration) @@ -103,9 +106,14 @@ namespace MyCompanyName.MyProjectName }); } - private static void ConfigureSwaggerServices(ServiceConfigurationContext context) + private static void ConfigureSwaggerServices(ServiceConfigurationContext context, IConfiguration configuration) { - context.Services.AddSwaggerGen( + context.Services.AddAbpSwaggerGenWithOAuth( + configuration["AuthServer:Authority"], + new Dictionary + { + {"MyProjectName", "MyProjectName API"} + }, options => { options.SwaggerDoc("v1", new OpenApiInfo {Title = "MyProjectName API", Version = "v1"}); @@ -198,11 +206,18 @@ namespace MyCompanyName.MyProjectName app.UseAuthorization(); app.UseSwagger(); - app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "MyProjectName API"); }); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "MyProjectName API"); + + var configuration = context.GetConfiguration(); + options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); + options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); + }); app.UseAuditing(); app.UseAbpSerilogEnrichers(); app.UseConfiguredEndpoints(); } } -} \ No newline at end of file +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json index ab4349b9c6..6c64a7b397 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json @@ -10,8 +10,10 @@ }, "AuthServer": { "Authority": "https://localhost:44301", - "RequireHttpsMetadata": "true" - }, + "RequireHttpsMetadata": "true", + "SwaggerClientId": "MyProjectName_Swagger", + "SwaggerClientSecret": "1q2w3e*" +}, "StringEncryption": { "DefaultPassPhrase": "gsKnGZ041HLL4IM8" }, diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj index d4cb35b5b0..05d08e875c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj @@ -13,12 +13,13 @@ - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs index 9f1b0c8168..231544bdbc 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; @@ -23,6 +24,7 @@ using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Swashbuckle; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.VirtualFileSystem; @@ -37,7 +39,8 @@ namespace MyCompanyName.MyProjectName typeof(AbpAspNetCoreMvcUiBasicThemeModule), typeof(AbpAspNetCoreAuthenticationJwtBearerModule), typeof(AbpAccountWebIdentityServerModule), - typeof(AbpAspNetCoreSerilogModule) + typeof(AbpAspNetCoreSerilogModule), + typeof(AbpSwashbuckleModule) )] public class MyProjectNameHttpApiHostModule : AbpModule { @@ -64,10 +67,7 @@ namespace MyCompanyName.MyProjectName { options.StyleBundles.Configure( BasicThemeBundles.Styles.Global, - bundle => - { - bundle.AddFiles("/global-styles.css"); - } + bundle => { bundle.AddFiles("/global-styles.css"); } ); }); } @@ -211,7 +211,10 @@ namespace MyCompanyName.MyProjectName app.UseAuthorization(); app.UseSwagger(); - app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "MyProjectName API"); }); + app.UseAbpSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyProjectName API"); + }); app.UseAuditing(); app.UseAbpSerilogEnrichers(); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj index 456b8ca312..66da2cc100 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj @@ -34,8 +34,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json index b2fa57365a..e9152eb6ad 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json @@ -1,7 +1,7 @@ { "App": { "SelfUrl": "https://localhost:44301", - "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307" + "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307,https://localhost:44300" }, "ConnectionStrings": { "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true" diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index 4b1b2b7e68..47dbf3b45b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -18,7 +18,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index 5c49b87251..7386e0aa09 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -34,7 +34,7 @@ - + diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj index b8837c8804..3e102fa6e3 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj @@ -22,7 +22,8 @@ - + + diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyProjectNameConsoleApiClientModule.cs b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyProjectNameConsoleApiClientModule.cs index a647c2d687..854cb49d36 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyProjectNameConsoleApiClientModule.cs +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyProjectNameConsoleApiClientModule.cs @@ -1,4 +1,8 @@ -using Volo.Abp.Http.Client.IdentityModel; +using System; +using Microsoft.Extensions.DependencyInjection; +using Polly; +using Volo.Abp.Http.Client; +using Volo.Abp.Http.Client.IdentityModel; using Volo.Abp.Modularity; namespace MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp @@ -9,6 +13,17 @@ namespace MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp )] public class MyProjectNameConsoleApiClientModule : AbpModule { - + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => + { + clientBuilder.AddTransientHttpErrorPolicy( + policyBuilder => policyBuilder.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i))) + ); + }); + }); + } } } diff --git a/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj b/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj index 65101175a6..4f91776d39 100644 --- a/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj +++ b/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj @@ -12,7 +12,7 @@ - + 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 cc0225c5eb..936ca98a8f 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,10 +8,10 @@ - - - - + + + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index 2d29e55d81..10097d1f3b 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -12,16 +12,16 @@ - - - - + + + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 1c1af02001..abe3a018bd 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -2,21 +2,19 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Security.Claims; using IdentityModel; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MyCompanyName.MyProjectName.EntityFrameworkCore; using MyCompanyName.MyProjectName.MultiTenancy; using StackExchange.Redis; using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; -using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; @@ -32,6 +30,7 @@ using Volo.Abp.MultiTenancy; using Volo.Abp.PermissionManagement.EntityFrameworkCore; using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.Swashbuckle; using Volo.Abp.VirtualFileSystem; namespace MyCompanyName.MyProjectName @@ -47,7 +46,8 @@ namespace MyCompanyName.MyProjectName typeof(AbpAuditLoggingEntityFrameworkCoreModule), typeof(AbpPermissionManagementEntityFrameworkCoreModule), typeof(AbpSettingManagementEntityFrameworkCoreModule), - typeof(AbpAspNetCoreSerilogModule) + typeof(AbpAspNetCoreSerilogModule), + typeof(AbpSwashbuckleModule) )] public class MyProjectNameHttpApiHostModule : AbpModule { @@ -79,10 +79,15 @@ namespace MyCompanyName.MyProjectName }); } - context.Services.AddSwaggerGen( + context.Services.AddAbpSwaggerGenWithOAuth( + configuration["AuthServer:Authority"], + new Dictionary + { + {"MyProjectName", "MyProjectName API"} + }, options => { - options.SwaggerDoc("v1", new OpenApiInfo { Title = "MyProjectName API", Version = "v1" }); + options.SwaggerDoc("v1", new OpenApiInfo {Title = "MyProjectName API", Version = "v1"}); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); }); @@ -179,6 +184,10 @@ namespace MyCompanyName.MyProjectName app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support APP API"); + + var configuration = context.GetConfiguration(); + options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); + options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); }); app.UseAuditing(); app.UseAbpSerilogEnrichers(); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json index d04f8779b8..d80fc69b59 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json @@ -11,6 +11,8 @@ }, "AuthServer": { "Authority": "https://localhost:44301/", - "RequireHttpsMetadata": "false" + "RequireHttpsMetadata": "false", + "SwaggerClientId": "MyProjectName_Swagger", + "SwaggerClientSecret": "1q2w3e*" } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/IdentityServer/IdentityServerDataSeedContributor.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/IdentityServer/IdentityServerDataSeedContributor.cs index f37a50da48..53a1827dba 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/IdentityServer/IdentityServerDataSeedContributor.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/IdentityServer/IdentityServerDataSeedContributor.cs @@ -166,7 +166,7 @@ namespace MyCompanyName.MyProjectName.IdentityServer secret: (configurationSection["MyProjectName_ConsoleTestApp:ClientSecret"] ?? "1q2w3e*").Sha256() ); } - + // Blazor Client var blazorClientId = configurationSection["MyProjectName_Blazor:ClientId"]; if (!blazorClientId.IsNullOrWhiteSpace()) @@ -183,6 +183,22 @@ namespace MyCompanyName.MyProjectName.IdentityServer postLogoutRedirectUri: $"{blazorRootUrl}/authentication/logout-callback" ); } + + // Swagger Client + var swaggerClientId = configurationSection["MyProjectName_Swagger:ClientId"]; + if (!swaggerClientId.IsNullOrWhiteSpace()) + { + var swaggerRootUrl = configurationSection["MyProjectName_Swagger:RootUrl"].TrimEnd('/'); + + await CreateClientAsync( + name: swaggerClientId, + scopes: commonScopes, + grantTypes: new[] { "authorization_code" }, + secret: configurationSection["MyProjectName_Swagger:ClientSecret"]?.Sha256(), + requireClientSecret: false, + redirectUri: $"{swaggerRootUrl}/swagger/oauth2-redirect.html" + ); + } } private async Task CreateClientAsync( diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj index 755eba603f..2ada24f679 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/appsettings.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/appsettings.json index efb325fd37..c3439a2d00 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/appsettings.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/appsettings.json @@ -1,7 +1,7 @@ { "App": { "SelfUrl": "https://localhost:44301/", - "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,http://localhost:44307,https://localhost:44307" + "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,http://localhost:44307,https://localhost:44307,https://localhost:44300" }, "AppSelfUrl": "https://localhost:44301/", "ConnectionStrings": { @@ -27,6 +27,11 @@ }, "MyProjectName_ConsoleTestApp": { "ClientId": "MyProjectName_ConsoleTestApp" + }, + "MyProjectName_Swagger": { + "ClientId": "MyProjectName_Swagger", + "ClientSecret": "1q2w3e*", + "RootUrl": "https://localhost:44300" } } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index c2c4b96125..54c7c5c0ad 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -12,7 +12,7 @@ - + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj index a63242f33b..2017892f0b 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj @@ -13,7 +13,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj index 3988d70e5e..b4569551d8 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj @@ -13,7 +13,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index 3db36aafe3..616e91d7c7 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -21,7 +21,7 @@ - + diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj index 8de0298aae..9809a39557 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj index 95c38a9087..8a48f8906d 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj @@ -20,7 +20,7 @@ - + diff --git a/templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj b/templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj index ccd53a9e86..b5b12c84bb 100644 --- a/templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj +++ b/templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj @@ -13,7 +13,7 @@ - +