Browse Source

Merge branch 'dev' into username-17750

pull/17755/head
maliming 3 years ago
parent
commit
7b5dbd02ed
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 98
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/POST.md
  2. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blog-detail.jpg
  3. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blogs.jpg
  4. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/cmskit-module-features.png
  5. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/homepage.png
  6. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery-detail.jpg
  7. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery.jpg
  8. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/menu-admin-side.jpg
  9. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/pages-admin-side.jpg
  10. BIN
      docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/products-abp-commercial.png
  11. 6
      docs/en/Dependency-Injection.md
  12. 53
      docs/en/Deployment/Configuring-OpenIddict.md
  13. 18
      framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/AbpConfigurationExtensions.cs
  14. 20
      framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostExtensions.cs
  15. 26
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueComparers/ExtraPropertyDictionaryValueComparer.cs
  16. 12
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs
  17. 28
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ExtraPropertyDictionaryConverts/ExtraPropertyDictionaryToFormData.cs
  18. 31
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ExtraPropertyDictionaryConverts/ExtraPropertyDictionaryToQueryString.cs
  19. 42
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs
  20. 5
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs
  21. 4
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs
  22. 5
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsInput.cs
  23. 14
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs
  24. 5
      modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml
  25. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs
  26. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs
  27. 3
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagRepository.cs
  28. 12
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreTagRepository.cs
  29. 13
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoTagRepository.cs
  30. 10
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs
  31. 2
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs
  32. 10
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs
  33. 2
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs
  34. 9
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs
  35. 1
      npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts
  36. 1
      npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts
  37. 6
      npm/ng-packs/packages/core/src/lib/tests/object-utils.spec.ts
  38. 4
      npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts
  39. 19
      npm/ng-packs/packages/core/src/lib/tests/utils/mock-compare-function.ts
  40. 9
      npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts
  41. 12
      npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts
  42. 2
      npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-actions.spec.ts
  43. 4
      npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-props.spec.ts
  44. 35
      npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts
  45. 6
      npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.spec.ts
  46. 11
      npm/ng-packs/packages/theme-shared/extensions/src/tests/localization.util.spec.ts
  47. 5
      npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts
  48. 5
      npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts
  49. 1
      npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts
  50. 3
      npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts
  51. 1
      npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts
  52. 45
      templates/app-nolayers/aspnet-core/README.md
  53. 39
      templates/app/aspnet-core/README.md
  54. 8
      templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs
  55. 23
      templates/console/src/MyCompanyName.MyProjectName/Program.cs
  56. 10
      templates/console/src/MyCompanyName.MyProjectName/Properties/launchSettings.json

98
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/POST.md

@ -0,0 +1,98 @@
# Unleash the Power of ABP CMS Kit: A Dynamic Content Showcase
We're excited to introduce to you [ABP](https://abp.io/)'s [CMS Kit Module](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Index) – a versatile module that empowers you to build your own dynamic content website with ease. In this introductory blog post, we'll first take a look at the **CMS Kit Module** and then we'll take you on a journey through our **CMS Kit Demo Application**, showcasing the incredible capabilities of this feature-rich module.
## Overview of the CMS Kit Module
At the heart of ABP's CMS Kit Module is a robust Content Management System (CMS) designed to simplify content creation and management. With the CMS Kit, you have the tools to build your own dynamic content website, complete with a range of features tailored to your specific needs. It provides core **building blocks** and fully working sub-systems to create your own website with the CMS features or use the building blocks in your websites for any purpose.
### CMS Kit: The Building Blocks (a.k.a Features)
The following features are currently available and ready to use:
* **Blogging**: Create your blog and publish posts (with markdown / HTML support)
* **Dynamic Pages**: Create pages with dynamic URLs (with markdown / HTML support)
* **Dynamic Menu**: Manage your application’s main menu on the fly
* **Tagging**: Tag any kind of content, like a blog post
* **Comments**: Allow users to comment and discuss in your application
* **Reactions**: Allow users to react to your content using simple smileys
* **Rating**: Reusable component to rate other contents
* **Global Resources**: Dynamically add CSS / JavaScript to your pages or blog posts
* **Dynamic Widgets**: Build widgets and use them in dynamic content, like blog posts
> For more information, please check the [CMS Kit Module documentation](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Index). In the documentation, you can see descriptions for each feature, learn how to install & configure the module, and much more...
### CMS Kit Pro: The Building Blocks (a.k.a Features)
CMS Kit Pro is a part of [ABP Commercial](https://commercial.abp.io/) and provides additional features. The following features are provided by the CMS Kit Pro Module:
* **Contact Form**: Easily add a «contact us» form to your website
* **Newsletter**: Allow users to subscribe to your newsletter (with multiple categories)
* **URL Forwarding**: Create short URLs or redirect users to other pages (for example: [abp.io/dapr](https://abp.io/dapr))
* **Poll**: Create quick polls for your users
* **Page Feedback**: Collect feedbacks from users for your content
> For more information, check the [CMS Kit Pro Module documentation](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index).
## Explore the CMS Kit Demo Application
As the core ABP development team, we've created a sample application to showcase the incredible capabilities of the ABP's CMS Kit Module. You can explore the source code of the application on our [GitHub repository](https://github.com/abpframework/cms-kit-demo) to get a deeper understanding of how the CMS Kit works under the hood. While developing the application, we aimed to build a real-world application and use almost all of the CMS Kit Features.
In the next sections, we will provide a detailed walkthrough of the application and highlight each CMS Kit feature that has been incorporated into it.
### Dynamic Menu Creation with CMS Kit's Menu System
One of the standing out features of the CMS Kit is its [Menu System](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Menus), which allows for the creation and dynamic ordering of application menu items. Say goodbye to static menus; with CMS Kit, you have the power to tailor your menu structure according to your evolving content needs.
You can see the homepage of the application in the following figure:
![](homepage.png)
The application menu items in the navbar are **created & ordered dynamically** with the [CMS Kit's Menu System](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Menus):
![](menu-admin-side.jpg)
### Custom Implementations with Comment & Reaction Features
Our demo application goes a step further by demonstrating custom implementations, such as an **image gallery**, seamlessly integrated with CMS Kit's [Comment](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments) & [Reaction](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Reactions) Features:
| Gallery | Detail Page |
|------------------------ |-----------------------|
| ![](image-gallery.jpg) | ![](image-gallery-detail.jpg) |
It's pretty easy to integrate CMS Kit Features such as Comments & Reactions into your existing pages. You can check the [source code of the application](https://github.com/abpframework/cms-kit-demo/blob/main/src/CmsKitDemo/Pages/Gallery/Detail.cshtml) and see how to integrate the features.
### Robust Blogging Capabilities
Blogging has never been easier! With the CMS Kit's [Blogging Feature](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Blogging), you can effortlessly manage your blog content, complete with [Ratings](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Ratings), [Comments](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments), [Tags](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Tags), and [Reactions](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Reactions) features as enabled:
| Blog | Blog Post |
|------------------------ |-----------------------|
| ![](blogs.jpg) | ![](blog-detail.jpg) |
You can enable/disable CMS Kit Features per blog on the admin side easily:
![](cmskit-module-features.png)
### Dynamic Pages with Style and Script Integration
*Products* pages showcase the flexibility of the CMS Kit's [Pages Feature](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Pages), allowing for dynamic content creation, style customization, and script integration. Your website can now truly reflect your unique brand and content style.
You can create pages with dynamic URLs on the admin side:
![](pages-admin-side.jpg)
After you have created the page, you can access it via `/{slug}` URL on the public-web side:
![](products-abp-commercial.png)
## What's Next?
Please try the CMS Kit Module now and provide [feedback](https://github.com/abpframework/abp) to help us to build a more effective content management kit!
## Resources
* [CMS Kit Demo: Source Code](https://github.com/abpframework/cms-kit-demo)
* [cms-kit-demo.abp.io](https://cms-kit-demo.abp.io/)
* [CMS Kit Module documentation](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Index)
* [CMS Kit Pro Module documentation](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index)

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blog-detail.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blogs.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/cmskit-module-features.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/homepage.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery-detail.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/menu-admin-side.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/pages-admin-side.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/products-abp-commercial.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

6
docs/en/Dependency-Injection.md

@ -1,12 +1,12 @@
# Dependency Injection # Dependency Injection
ABP's Dependency Injection system is developed based on Microsoft's [dependency injection extension](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) library (Microsoft.Extensions.DependencyInjection nuget package). So, it's documentation is valid in ABP too. ABP's Dependency Injection system is developed based on Microsoft's [dependency injection extension](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) library (Microsoft.Extensions.DependencyInjection nuget package). So, its documentation is valid in ABP too.
> While ABP has no core dependency to any 3rd-party DI provider. However, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. Startup templates come with [Autofac](https://autofac.org/) installed. See [Autofac integration](Autofac-Integration.md) document for more information. > While ABP has no core dependency to any 3rd-party DI provider. However, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. Startup templates come with [Autofac](https://autofac.org/) installed. See [Autofac integration](Autofac-Integration.md) document for more information.
## Modularity ## Modularity
Since ABP is a modular framework, every module defines it's own services and registers via dependency injection in it's own separate [module class](Module-Development-Basics.md). Example: Since ABP is a modular framework, every module defines its own services and registers via dependency injection in its own separate [module class](Module-Development-Basics.md). Example:
````C# ````C#
public class BlogModule : AbpModule public class BlogModule : AbpModule
@ -210,7 +210,7 @@ public class TaxAppService : ApplicationService
} }
```` ````
``TaxAppService`` gets ``ITaxCalculator`` in it's constructor. The dependency injection system automatically provides the requested service at runtime. ``TaxAppService`` gets ``ITaxCalculator`` in its constructor. The dependency injection system automatically provides the requested service at runtime.
Constructor injection is preffered way of injecting dependencies to a class. In that way, the class can not be constructed unless all constructor-injected dependencies are provided. Thus, the class explicitly declares it's required services. Constructor injection is preffered way of injecting dependencies to a class. In that way, the class can not be constructed unless all constructor-injected dependencies are provided. Thus, the class explicitly declares it's required services.

53
docs/en/Deployment/Configuring-OpenIddict.md

@ -2,61 +2,46 @@
This document introduces how to configure `OpenIddict` in the `AuthServer` project. This document introduces how to configure `OpenIddict` in the `AuthServer` project.
There are different configurations in the `AuthServer` project for `Development` and `Production` environment. There are different configurations in the `AuthServer` project for the `Development` and `Production` environments.
````csharp ````csharp
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
var hostingEnvironment = context.Services.GetHostingEnvironment(); var hostingEnvironment = context.Services.GetHostingEnvironment();
// Development environment
if (hostingEnvironment.IsDevelopment())
{
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
{
// This is default value, you can remove this line.
options.AddDevelopmentEncryptionAndSigningCertificate = true;
});
}
// Production or Staging environment
if (!hostingEnvironment.IsDevelopment()) if (!hostingEnvironment.IsDevelopment())
{ {
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options => PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
{ {
options.AddDevelopmentEncryptionAndSigningCertificate = false; options.AddDevelopmentEncryptionAndSigningCertificate = false;
}); });
PreConfigure<OpenIddictServerBuilder>(builder => PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{ {
builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment)); serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", "00000000-0000-0000-0000-000000000000");
builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment)); });
//...
});
} }
} }
private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv)
{
return new X509Certificate2(Path.Combine(hostingEnv.ContentRootPath, "authserver.pfx"), "00000000-0000-0000-0000-000000000000");
}
```` ````
## Development Environment ## Development Environment
We've enabled `AddDevelopmentEncryptionAndSigningCertificate` by default on development environment, It registers (and generates if necessary) a user-specific development encryption/development signing certificate. This is a certificate used for signing and encrypting the tokens and for **development environment only**. We've enabled `AddDevelopmentEncryptionAndSigningCertificate` by default on the development environment. It registers (and generates if necessary) a user-specific development encryption/development signing certificate. This is a certificate used for signing and encrypting the tokens and for **development environment only**.
`AddDevelopmentEncryptionAndSigningCertificate` cannot be used in applications deployed on IIS or Azure App Service: trying to use them on IIS or Azure App Service will result in an exception being thrown at runtime (unless the application pool is configured to [load a user profile](https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#user-profile)). `AddDevelopmentEncryptionAndSigningCertificate` cannot be used in applications deployed on IIS or Azure App Service: trying to use them on IIS or Azure App Service will result in an exception being thrown at runtime (unless the application pool is configured to [load a user profile](https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#user-profile)).
To avoid that, consider creating self-signed certificates and storing them in the X.509 certificates storage of the host machine(s). This is the way we do it in production environment. To avoid that, consider creating self-signed certificates and storing them in the X.509 certificates storage of the host machine(s). This is the way we do it in the production environment.
## Production Environment ## Production Environment
We've disabled `AddDevelopmentEncryptionAndSigningCertificate` in production environment and tried to setup signing and encrypting certificates using `authserver.pfx`. We've disabled `AddDevelopmentEncryptionAndSigningCertificate` in the production environment and tried to setup signing and encrypting certificates using `openiddict.pfx` file.
You can use the `dotnet dev-certs https -v -ep authserver.pfx -p 00000000-0000-0000-0000-000000000000` command to generate the `authserver.pfx` certificate. You can use the `dotnet dev-certs https -v -ep openiddict.pfx -p 00000000-0000-0000-0000-000000000000` command to generate the `authserver.pfx` certificate.
> `00000000-0000-0000-0000-000000000000` is the password of the certificate, you can change it to any password you want. > `00000000-0000-0000-0000-000000000000` is the password of the certificate, you can change it to any password you want.
> Also, please remember to copy `authserver.pfx` to the [Content Root Folder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.ihostingenvironment.contentrootpath?view=aspnetcore-7.0) of the `AuthServer` website. > Also, please remember to copy `openiddict.pfx` to the [Content Root Folder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.ihostingenvironment.contentrootpath?view=aspnetcore-7.0) of the `AuthServer` website.
> It is recommended to use **two** RSA certificates, distinct from the certificate(s) used for HTTPS: one for encryption, one for signing.
For more information, please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-certificate-recommended-for-production-ready-scenarios

18
framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/AbpConfigurationExtensions.cs

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.Hosting;
namespace Microsoft.Extensions.Configuration;
public static class AbpConfigurationExtensions
{
public static IConfigurationBuilder AddAppSettingsSecretsJson(
this IConfigurationBuilder builder,
bool optional = true,
bool reloadOnChange = true,
string path = AbpHostingHostBuilderExtensions.AppSettingsSecretJsonPath)
{
return builder.AddJsonFile(path: path, optional: optional, reloadOnChange: reloadOnChange);
}
}

20
framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostExtensions.cs

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Threading;
namespace Microsoft.Extensions.Hosting;
public static class AbpHostExtensions
{
public static async Task InitializeAsync(this IHost host)
{
var application = host.Services.GetRequiredService<IAbpApplicationWithExternalServiceProvider>();
var applicationLifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
applicationLifetime.ApplicationStopping.Register(() => AsyncHelper.RunSync(() => application.ShutdownAsync()));
applicationLifetime.ApplicationStopped.Register(() => application.Dispose());
await application.InitializeAsync(host.Services);
}
}

26
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueComparers/ExtraPropertyDictionaryValueComparer.cs

@ -9,9 +9,29 @@ public class ExtraPropertyDictionaryValueComparer : ValueComparer<ExtraPropertyD
{ {
public ExtraPropertyDictionaryValueComparer() public ExtraPropertyDictionaryValueComparer()
: base( : base(
(d1, d2) => d1!.SequenceEqual(d2!), (a, b) => Compare(a, b),
d => d.Aggregate(0, (k, v) => HashCode.Combine(k, v.GetHashCode())), d => d.Aggregate(0, (k, v) => HashCode.Combine(k, v.GetHashCode())),
d => new ExtraPropertyDictionary(d)) d => new ExtraPropertyDictionary(d))
{ {
} }
private static bool Compare(ExtraPropertyDictionary? a, ExtraPropertyDictionary? b)
{
if (ReferenceEquals(a, b))
{
return true;
}
if (a is null)
{
return b is null;
}
if (b is null)
{
return false;
}
return a!.SequenceEqual(b!);
}
} }

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

@ -1,11 +1,15 @@
using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Castle; using Volo.Abp.Castle;
using Volo.Abp.Data;
using Volo.Abp.EventBus; using Volo.Abp.EventBus;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading; using Volo.Abp.Threading;
using Volo.Abp.Validation; using Volo.Abp.Validation;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.Http.Client.ClientProxying.ExtraPropertyDictionaryConverts;
using Volo.Abp.Http.Client.DynamicProxying; using Volo.Abp.Http.Client.DynamicProxying;
using Volo.Abp.RemoteServices; using Volo.Abp.RemoteServices;
@ -27,5 +31,11 @@ public class AbpHttpClientModule : AbpModule
{ {
context.Services.AddHttpClient(); context.Services.AddHttpClient();
context.Services.AddTransient(typeof(DynamicHttpProxyInterceptorClientProxy<>)); context.Services.AddTransient(typeof(DynamicHttpProxyInterceptorClientProxy<>));
Configure<AbpHttpClientProxyingOptions>(options =>
{
options.QueryStringConverts.Add(typeof(ExtraPropertyDictionary), typeof(ExtraPropertyDictionaryToQueryString));
options.FormDataConverts.Add(typeof(ExtraPropertyDictionary), typeof(ExtraPropertyDictionaryToFormData));
});
} }
} }

28
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ExtraPropertyDictionaryConverts/ExtraPropertyDictionaryToFormData.cs

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Modeling;
namespace Volo.Abp.Http.Client.ClientProxying.ExtraPropertyDictionaryConverts;
public class ExtraPropertyDictionaryToFormData : IObjectToFormData<ExtraPropertyDictionary>, ITransientDependency
{
public Task<List<KeyValuePair<string, HttpContent>>> ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, ExtraPropertyDictionary extraPropertyDictionary)
{
if (extraPropertyDictionary.IsNullOrEmpty())
{
return Task.FromResult<List<KeyValuePair<string, HttpContent>>>(null!);
}
var formDataContents = new List<KeyValuePair<string, HttpContent>>();
foreach (var item in extraPropertyDictionary)
{
formDataContents.Add(new KeyValuePair<string, HttpContent>($"ExtraProperties[{item.Key}]", new StringContent(item.Value!.ToString()!, Encoding.UTF8)));
}
return Task.FromResult(formDataContents);
}
}

31
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ExtraPropertyDictionaryConverts/ExtraPropertyDictionaryToQueryString.cs

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Modeling;
namespace Volo.Abp.Http.Client.ClientProxying.ExtraPropertyDictionaryConverts;
public class ExtraPropertyDictionaryToQueryString : IObjectToQueryString<ExtraPropertyDictionary>, ITransientDependency
{
public Task<string> ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, ExtraPropertyDictionary extraPropertyDictionary)
{
if (extraPropertyDictionary.IsNullOrEmpty())
{
return Task.FromResult<string>(null!);
}
var sb = new StringBuilder();
foreach (var item in extraPropertyDictionary)
{
sb.Append($"ExtraProperties[{item.Key}]={item.Value}&");
}
if (sb.Length > 0)
{
sb.Remove(sb.Length - 1, 1);
}
return Task.FromResult(sb.ToString());
}
}

42
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs

@ -12,6 +12,7 @@ using Shouldly;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.Content; using Volo.Abp.Content;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Http.Client; using Volo.Abp.Http.Client;
using Volo.Abp.TestApp.Application; using Volo.Abp.TestApp.Application;
@ -280,7 +281,7 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase
[Fact] [Fact]
public async Task GetParamsFromQueryAsync() public async Task GetParamsFromQueryAsync()
{ {
var result = await _peopleAppService.GetParamsFromQueryAsync(new GetParamsInput() var input = new GetParamsInput()
{ {
NameValues = new List<GetParamsNameValue>() NameValues = new List<GetParamsNameValue>()
{ {
@ -300,34 +301,39 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase
Name = "name3", Name = "name3",
Value = "value3" Value = "value3"
} }
}); };
result.ShouldBe("name1-value1:name2-value2:name3-value3"); input.SetProperty("TestProperty", "TestPropertyValue");
foreach (var nameValue in input.NameValues)
{
nameValue.SetProperty("TestPropertyInList", "TestPropertyValueInList");
}
var result = await _peopleAppService.GetParamsFromQueryAsync(input);
result.ShouldBe("name1-value1:TestPropertyValueInList:name2-value2:name3-value3:TestPropertyValue");
} }
[Fact] [Fact]
public async Task GetParamsFromFormAsync() public async Task GetParamsFromFormAsync()
{ {
var result = await _peopleAppService.GetParamsFromFormAsync(new GetParamsInput() var input = new GetParamsInput()
{ {
NameValues = new List<GetParamsNameValue>() NameValues = new List<GetParamsNameValue>()
{ {
new GetParamsNameValue() new GetParamsNameValue() {Name = "name1", Value = "value1"},
{ new GetParamsNameValue() {Name = "name2", Value = "value2"}
Name = "name1",
Value = "value1"
},
new GetParamsNameValue()
{
Name = "name2",
Value = "value2"
}
}, },
NameValue = new GetParamsNameValue() NameValue = new GetParamsNameValue()
{ {
Name = "name3", Name = "name3", Value = "value3"
Value = "value3"
} }
}); };
result.ShouldBe("name1-value1:name2-value2:name3-value3"); input.SetProperty("TestProperty", "TestPropertyValue");
foreach (var nameValue in input.NameValues)
{
nameValue.SetProperty("TestPropertyInList", "TestPropertyValueInList");
}
var result = await _peopleAppService.GetParamsFromFormAsync(input);
result.ShouldBe("name1-value1:TestPropertyValueInList:name2-value2:name3-value3:TestPropertyValue");
} }
} }

5
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs

@ -23,6 +23,11 @@ public class TestObjectToFormData : IObjectToFormData<List<GetParamsNameValue>>,
{ {
formDataContents.Add(new KeyValuePair<string, HttpContent>($"NameValues[{i}].Name", new StringContent(values[i].Name, Encoding.UTF8))); formDataContents.Add(new KeyValuePair<string, HttpContent>($"NameValues[{i}].Name", new StringContent(values[i].Name, Encoding.UTF8)));
formDataContents.Add(new KeyValuePair<string, HttpContent>($"NameValues[{i}].Value", new StringContent(values[i].Value, Encoding.UTF8))); formDataContents.Add(new KeyValuePair<string, HttpContent>($"NameValues[{i}].Value", new StringContent(values[i].Value, Encoding.UTF8)));
foreach (var item in values[i].ExtraProperties)
{
formDataContents.Add(new KeyValuePair<string, HttpContent>($"NameValues[{i}].ExtraProperties[{item.Key}]", new StringContent(item.Value!.ToString()!, Encoding.UTF8)));
}
} }
return Task.FromResult(formDataContents); return Task.FromResult(formDataContents);

4
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs

@ -22,6 +22,10 @@ public class TestObjectToQueryString : IObjectToQueryString<List<GetParamsNameVa
for (var i = 0; i < values.Count; i++) for (var i = 0; i < values.Count; i++)
{ {
sb.Append($"NameValues[{i}].Name={values[i].Name}&NameValues[{i}].Value={values[i].Value}&"); sb.Append($"NameValues[{i}].Name={values[i].Name}&NameValues[{i}].Value={values[i].Value}&");
foreach (var item in values[i].ExtraProperties)
{
sb.Append($"NameValues[{i}].ExtraProperties[{item.Key}]={item.Value}&");
}
} }
sb.Remove(sb.Length - 1, 1); sb.Remove(sb.Length - 1, 1);

5
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsInput.cs

@ -1,15 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.TestApp.Application.Dto; namespace Volo.Abp.TestApp.Application.Dto;
public class GetParamsInput public class GetParamsInput : ExtensibleObject
{ {
public List<GetParamsNameValue> NameValues { get; set; } public List<GetParamsNameValue> NameValues { get; set; }
public GetParamsNameValue NameValue { get; set; } public GetParamsNameValue NameValue { get; set; }
} }
public class GetParamsNameValue public class GetParamsNameValue : ExtensibleObject
{ {
public string Name { get; set; } public string Name { get; set; }

14
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs

@ -129,17 +129,19 @@ public class PeopleAppService : CrudAppService<Person, PersonDto, Guid>, IPeople
public Task<string> GetParamsFromQueryAsync([FromQuery] GetParamsInput input) public Task<string> GetParamsFromQueryAsync([FromQuery] GetParamsInput input)
{ {
return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + input.NameValues?.FirstOrDefault()?.Value + ":" +
input.NameValues?.FirstOrDefault()?.Value + ":" + input.NameValues?.FirstOrDefault()?.ExtraProperties["TestPropertyInList"] + ":" +
input.NameValues?.LastOrDefault()?.Name + "-" + input.NameValues?.LastOrDefault()?.Value + ":" + input.NameValues?.LastOrDefault()?.Name + "-" + input.NameValues?.LastOrDefault()?.Value + ":" +
input.NameValue?.Name + "-" + input.NameValue?.Value); input.NameValue?.Name + "-" + input.NameValue?.Value + ":" +
input.ExtraProperties["TestProperty"]);
} }
public Task<string> GetParamsFromFormAsync([FromForm] GetParamsInput input) public Task<string> GetParamsFromFormAsync([FromForm] GetParamsInput input)
{ {
return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + input.NameValues?.FirstOrDefault()?.Value + ":" +
input.NameValues?.FirstOrDefault()?.Value + ":" + input.NameValues?.FirstOrDefault()?.ExtraProperties["TestPropertyInList"] + ":" +
input.NameValues?.LastOrDefault()?.Name + "-" + input.NameValues?.LastOrDefault()?.Value + ":" + input.NameValues?.LastOrDefault()?.Name + "-" + input.NameValues?.LastOrDefault()?.Value + ":" +
input.NameValue?.Name + "-" + input.NameValue?.Value); input.NameValue?.Name + "-" + input.NameValue?.Value + ":" +
input.ExtraProperties["TestProperty"]);
} }
} }

5
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml

@ -31,10 +31,7 @@
<a class="nav-link dropdown-toggle" href="#" id="Menu_@(menuItem.Name)" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="Menu_@(menuItem.Name)" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@if (menuItem.Icon != null) @if (menuItem.Icon != null)
{ {
if (menuItem.Icon.StartsWith("fa")) <i class="@menuItem.Icon"></i>
{
<i class="@menuItem.Icon"></i>
}
} }
@menuItem.DisplayName @menuItem.DisplayName
</a> </a>

2
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs

@ -92,7 +92,7 @@ public class TagAdminAppService : CmsKitAppServiceBase, ITagAdminAppService
[Authorize(CmsKitAdminPermissions.Tags.Default)] [Authorize(CmsKitAdminPermissions.Tags.Default)]
public virtual async Task<PagedResultDto<TagDto>> GetListAsync(TagGetListInput input) public virtual async Task<PagedResultDto<TagDto>> GetListAsync(TagGetListInput input)
{ {
var tags = await Repository.GetListAsync(input.Filter); var tags = await Repository.GetListAsync(input.Filter, input.MaxResultCount, input.SkipCount, input.Sorting);
var count = await Repository.GetCountAsync(input.Filter); var count = await Repository.GetCountAsync(input.Filter);
return new PagedResultDto<TagDto>( return new PagedResultDto<TagDto>(

2
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs

@ -97,7 +97,7 @@ public class CmsKitAdminMenuContributor : IMenuContributor
CmsKitAdminMenus.GlobalResources.GlobalResourcesMenu, CmsKitAdminMenus.GlobalResources.GlobalResourcesMenu,
l["GlobalResources"], l["GlobalResources"],
"/Cms/GlobalResources", "/Cms/GlobalResources",
"bi bi-code-slash", "fa fa-code",
order: 4) order: 4)
.RequireFeatures(CmsKitFeatures.GlobalResourceEnable) .RequireFeatures(CmsKitFeatures.GlobalResourceEnable)
.RequireGlobalFeatures(typeof(GlobalResourcesFeature)) .RequireGlobalFeatures(typeof(GlobalResourcesFeature))

3
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagRepository.cs

@ -26,6 +26,9 @@ public interface ITagRepository : IBasicRepository<Tag, Guid>
Task<List<Tag>> GetListAsync( Task<List<Tag>> GetListAsync(
string filter, string filter,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string sorting = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
Task<int> GetCountAsync( Task<int> GetCountAsync(

12
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreTagRepository.cs

@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
@ -96,9 +97,16 @@ public class EfCoreTagRepository : EfCoreRepository<ICmsKitDbContext, Tag, Guid>
.ToListAsync(cancellationToken: GetCancellationToken(cancellationToken)); .ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));
} }
public virtual async Task<List<Tag>> GetListAsync(string filter, CancellationToken cancellationToken = default) public virtual async Task<List<Tag>> GetListAsync(
string filter,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string sorting = null,
CancellationToken cancellationToken = default)
{ {
return await (await GetQueryableByFilterAsync(filter)).ToListAsync(GetCancellationToken(cancellationToken)); return await (await GetQueryableByFilterAsync(filter))
.OrderBy(sorting.IsNullOrEmpty() ? $"{nameof(Tag.CreationTime)}" : sorting)
.PageBy(skipCount, maxResultCount).ToListAsync(GetCancellationToken(cancellationToken));
} }
public virtual async Task<int> GetCountAsync(string filter, CancellationToken cancellationToken = default) public virtual async Task<int> GetCountAsync(string filter, CancellationToken cancellationToken = default)

13
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoTagRepository.cs

@ -4,6 +4,7 @@ using MongoDB.Driver.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
@ -107,9 +108,17 @@ public class MongoTagRepository : MongoDbRepository<ICmsKitMongoDbContext, Volo.
} }
public virtual async Task<List<Tag>> GetListAsync(string filter, CancellationToken cancellationToken = default) public virtual async Task<List<Tag>> GetListAsync(string filter,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string sorting = null,
CancellationToken cancellationToken = default)
{ {
return await (await GetQueryableByFilterAsync(filter, cancellationToken)).ToListAsync(GetCancellationToken(cancellationToken)); return await (await GetQueryableByFilterAsync(filter, cancellationToken))
.OrderBy(sorting.IsNullOrEmpty() ? $"{nameof(Tag.CreationTime)}" : sorting)
.As<IMongoQueryable<Tag>>()
.PageBy<Tag, IMongoQueryable<Tag>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
} }
public virtual async Task<int> GetCountAsync(string filter, CancellationToken cancellationToken = default) public virtual async Task<int> GetCountAsync(string filter, CancellationToken cancellationToken = default)

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

@ -188,11 +188,11 @@ public class EfCoreIdentityUserRepository : EfCoreRepository<IIdentityDbContext,
) )
.WhereIf(roleId.HasValue, identityUser => identityUser.Roles.Any(x => x.RoleId == roleId.Value)) .WhereIf(roleId.HasValue, identityUser => identityUser.Roles.Any(x => x.RoleId == roleId.Value))
.WhereIf(organizationUnitId.HasValue, identityUser => identityUser.OrganizationUnits.Any(x => x.OrganizationUnitId == organizationUnitId.Value)) .WhereIf(organizationUnitId.HasValue, identityUser => identityUser.OrganizationUnits.Any(x => x.OrganizationUnitId == organizationUnitId.Value))
.WhereIf(!string.IsNullOrWhiteSpace(userName), x => x.UserName == userName) .WhereIf(!string.IsNullOrWhiteSpace(userName), x => x.UserName.Contains(userName))
.WhereIf(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber == phoneNumber) .WhereIf(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber.Contains(phoneNumber))
.WhereIf(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email == emailAddress) .WhereIf(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email.Contains(emailAddress))
.WhereIf(!string.IsNullOrWhiteSpace(name), x => x.Name == name) .WhereIf(!string.IsNullOrWhiteSpace(name), x => x.Name.Contains(name))
.WhereIf(!string.IsNullOrWhiteSpace(surname), x => x.Surname == surname) .WhereIf(!string.IsNullOrWhiteSpace(surname), x => x.Surname.Contains(surname))
.WhereIf(isLockedOut.HasValue, x => (x.LockoutEnabled && x.LockoutEnd.HasValue && x.LockoutEnd.Value.CompareTo(DateTime.UtcNow) > 0) == isLockedOut.Value) .WhereIf(isLockedOut.HasValue, x => (x.LockoutEnabled && x.LockoutEnd.HasValue && x.LockoutEnd.Value.CompareTo(DateTime.UtcNow) > 0) == isLockedOut.Value)
.WhereIf(notActive.HasValue, x => x.IsActive == !notActive.Value) .WhereIf(notActive.HasValue, x => x.IsActive == !notActive.Value)
.WhereIf(emailConfirmed.HasValue, x => x.EmailConfirmed == emailConfirmed.Value) .WhereIf(emailConfirmed.HasValue, x => x.EmailConfirmed == emailConfirmed.Value)

2
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs

@ -40,7 +40,7 @@ public class EfCoreOrganizationUnitRepository
{ {
return await (await GetDbSetAsync()) return await (await GetDbSetAsync())
.IncludeDetails(includeDetails) .IncludeDetails(includeDetails)
.Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value) .Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
} }

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

@ -168,11 +168,11 @@ public class MongoIdentityUserRepository : MongoDbRepository<IAbpIdentityMongoDb
) )
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(roleId.HasValue, identityUser => identityUser.Roles.Any(x => x.RoleId == roleId.Value)) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(roleId.HasValue, identityUser => identityUser.Roles.Any(x => x.RoleId == roleId.Value))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(organizationUnitId.HasValue, identityUser => identityUser.OrganizationUnits.Any(x => x.OrganizationUnitId == organizationUnitId.Value)) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(organizationUnitId.HasValue, identityUser => identityUser.OrganizationUnits.Any(x => x.OrganizationUnitId == organizationUnitId.Value))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(userName), x => x.UserName == userName) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(userName), x => x.UserName.Contains(userName))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber == phoneNumber) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber.Contains(phoneNumber))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email == emailAddress) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email.Contains(emailAddress))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(name), x => x.Name == name) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(name), x => x.Name.Contains(name))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(surname), x => x.Surname == surname) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(!string.IsNullOrWhiteSpace(surname), x => x.Surname.Contains(surname))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(isLockedOut.HasValue && isLockedOut.Value, x => x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(isLockedOut.HasValue && isLockedOut.Value, x => x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow)
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(isLockedOut.HasValue && !isLockedOut.Value, x => !(x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow)) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(isLockedOut.HasValue && !isLockedOut.Value, x => !(x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(notActive.HasValue, x => x.IsActive == !notActive.Value) .WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(notActive.HasValue, x => x.IsActive == !notActive.Value)

2
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs

@ -39,7 +39,7 @@ public class MongoOrganizationUnitRepository
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetMongoQueryableAsync(cancellationToken))
.Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value) .Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
} }

9
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs

@ -47,8 +47,13 @@ public abstract class OrganizationUnitRepository_Tests<TStartupModule> : AbpIden
[Fact] [Fact]
public async Task GetAllChildrenWithParentCodeAsync() public async Task GetAllChildrenWithParentCodeAsync()
{ {
(await _organizationUnitRepository.GetAllChildrenWithParentCodeAsync(OrganizationUnit.CreateCode(0), var allChildren = await _organizationUnitRepository.GetAllChildrenWithParentCodeAsync(OrganizationUnit.CreateCode(0), _guidGenerator.Create());
_guidGenerator.Create())).ShouldNotBeNull(); allChildren.ShouldNotBeNull();
allChildren.ShouldBeEmpty();
allChildren = (await _organizationUnitRepository.GetAllChildrenWithParentCodeAsync(OrganizationUnit.CreateCode(1), null));
allChildren.ShouldNotBeNull();
allChildren.ShouldNotBeEmpty();
} }
[Fact] [Fact]

1
npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts

@ -10,7 +10,6 @@ describe('ContentProjectionService', () => {
// createServiceFactory does not accept entryComponents directly // createServiceFactory does not accept entryComponents directly
@NgModule({ @NgModule({
declarations: [TestComponent], declarations: [TestComponent],
entryComponents: [TestComponent],
}) })
class TestModule {} class TestModule {}

1
npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts

@ -36,7 +36,6 @@ const LAYOUTS = [
@NgModule({ @NgModule({
imports: [RouterModule], imports: [RouterModule],
declarations: [...LAYOUTS], declarations: [...LAYOUTS],
entryComponents: [...LAYOUTS],
}) })
class DummyLayoutModule {} class DummyLayoutModule {}

6
npm/ng-packs/packages/core/src/lib/tests/object-utils.spec.ts

@ -43,11 +43,9 @@ describe('DeepMerge', () => {
); );
it('should correctly return when both inputs are objects with different fields', () => { it('should correctly return when both inputs are objects with different fields', () => {
const target = { a: 1 };
const source = { b: 2 };
const expected = { a: 1, b: 2 }; const expected = { a: 1, b: 2 };
expect(deepMerge(target, source)).toEqual(expected); expect(deepMerge({ a: 1 }, { b: 2 })).toEqual(expected);
expect(deepMerge(source, target)).toEqual(expected); expect(deepMerge({ b: 2 }, { a: 1 })).toEqual(expected);
}); });
it('should correctly return when both inputs are object with same fields but different values', () => { it('should correctly return when both inputs are object with same fields but different values', () => {

4
npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts

@ -3,14 +3,15 @@ import { take } from 'rxjs/operators';
import { RoutesService } from '../services/routes.service'; import { RoutesService } from '../services/routes.service';
import { DummyInjector } from './utils/common.utils'; import { DummyInjector } from './utils/common.utils';
import { mockPermissionService } from './utils/permission-service.spec.utils'; import { mockPermissionService } from './utils/permission-service.spec.utils';
import { mockCompareFunction } from './utils/mock-compare-function';
const updateStream$ = new Subject<void>(); const updateStream$ = new Subject<void>();
export const mockRoutesService = (injectorPayload = {} as { [key: string]: any }) => { export const mockRoutesService = (injectorPayload = {} as { [key: string]: any }) => {
const injector = new DummyInjector({ const injector = new DummyInjector({
PermissionService: mockPermissionService(), PermissionService: mockPermissionService(),
ConfigStateService: { createOnUpdateStream: () => updateStream$ }, ConfigStateService: { createOnUpdateStream: () => updateStream$ },
OTHERS_GROUP: 'OthersGroup', OTHERS_GROUP: 'OthersGroup',
SORT_COMPARE_FUNC: mockCompareFunction,
...injectorPayload, ...injectorPayload,
}); });
return new RoutesService(injector); return new RoutesService(injector);
@ -50,7 +51,6 @@ describe('Routes Service', () => {
const flat = await lastValueFrom(service.flat$.pipe(take(1))); const flat = await lastValueFrom(service.flat$.pipe(take(1)));
const tree = await lastValueFrom(service.tree$.pipe(take(1))); const tree = await lastValueFrom(service.tree$.pipe(take(1)));
const visible = await lastValueFrom(service.visible$.pipe(take(1))); const visible = await lastValueFrom(service.visible$.pipe(take(1)));
expect(flat.length).toBe(5); expect(flat.length).toBe(5);
expect(flat[0].name).toBe('baz'); expect(flat[0].name).toBe('baz');
expect(flat[1].name).toBe('qux'); expect(flat[1].name).toBe('qux');

19
npm/ng-packs/packages/core/src/lib/tests/utils/mock-compare-function.ts

@ -0,0 +1,19 @@
import { ABP } from '@abp/ng.core';
export const mockCompareFunction = (a: ABP.Route, b: ABP.Route) => {
const aName = a.name;
const bName = b.name;
const aNumber = a.order;
const bNumber = b.order;
if (!Number.isInteger(aNumber)) return 1;
if (!Number.isInteger(bNumber)) return -1;
if (aNumber > bNumber) return 1
if (aNumber < bNumber) return -1
if ( aName > bName ) return 1;
if ( aName < bName ) return -1;
return 0
}

9
npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts

@ -3,11 +3,12 @@ import { SpyObject } from '@ngneat/spectator';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { Subject, timer } from 'rxjs'; import { Subject, timer } from 'rxjs';
import { ApiInterceptor, HttpWaitService, SessionStateService, TENANT_KEY } from '@abp/ng.core'; import { HttpWaitService, SessionStateService, TENANT_KEY } from '@abp/ng.core';
import { OAuthApiInterceptor } from '../interceptors';
describe('ApiInterceptor', () => { describe('ApiInterceptor', () => {
let spectator: SpectatorService<ApiInterceptor>; let spectator: SpectatorService<OAuthApiInterceptor>;
let interceptor: ApiInterceptor; let interceptor: OAuthApiInterceptor;
let oauthService: SpyObject<OAuthService>; let oauthService: SpyObject<OAuthService>;
let sessionState: SpyObject<SessionStateService>; let sessionState: SpyObject<SessionStateService>;
let httpWaitService: SpyObject<HttpWaitService>; let httpWaitService: SpyObject<HttpWaitService>;
@ -15,7 +16,7 @@ describe('ApiInterceptor', () => {
const testTenantKey = 'TEST_TENANT_KEY'; const testTenantKey = 'TEST_TENANT_KEY';
const createService = createServiceFactory({ const createService = createServiceFactory({
service: ApiInterceptor, service: OAuthApiInterceptor,
mocks: [OAuthService, SessionStateService], mocks: [OAuthService, SessionStateService],
providers: [{ provide: TENANT_KEY, useValue: testTenantKey }], providers: [{ provide: TENANT_KEY, useValue: testTenantKey }],
}); });

12
npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts

@ -1,11 +1,15 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; import { createServiceFactory, SpectatorService, createSpyObject } from '@ngneat/spectator/jest';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { AbpOAuthGuard } from '../guards/oauth.guard'; import { AbpOAuthGuard } from '../guards/oauth.guard';
import { AuthService } from '@abp/ng.core'; import { AuthService } from '@abp/ng.core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
describe('AuthGuard', () => { describe('AuthGuard', () => {
let spectator: SpectatorService<AbpOAuthGuard>; let spectator: SpectatorService<AbpOAuthGuard>;
let guard: AbpOAuthGuard; let guard : AbpOAuthGuard;
let route = createSpyObject<ActivatedRouteSnapshot>(ActivatedRouteSnapshot)
let state = createSpyObject<RouterStateSnapshot>(RouterStateSnapshot)
const createService = createServiceFactory({ const createService = createServiceFactory({
service: AbpOAuthGuard, service: AbpOAuthGuard,
mocks: [OAuthService, AuthService], mocks: [OAuthService, AuthService],
@ -18,7 +22,7 @@ describe('AuthGuard', () => {
it('should return true when user logged in', () => { it('should return true when user logged in', () => {
spectator.inject(OAuthService).hasValidAccessToken.andReturn(true); spectator.inject(OAuthService).hasValidAccessToken.andReturn(true);
expect(guard.canActivate()).toBe(true); expect(guard.canActivate(route, state)).toBe(true);
}); });
it('should execute the navigateToLogin method of the authService', () => { it('should execute the navigateToLogin method of the authService', () => {
@ -26,7 +30,7 @@ describe('AuthGuard', () => {
spectator.inject(OAuthService).hasValidAccessToken.andReturn(false); spectator.inject(OAuthService).hasValidAccessToken.andReturn(false);
const navigateToLoginSpy = jest.spyOn(authService, 'navigateToLogin'); const navigateToLoginSpy = jest.spyOn(authService, 'navigateToLogin');
expect(guard.canActivate()).toBe(false); expect(guard.canActivate(route, state)).toBe(false);
expect(navigateToLoginSpy).toHaveBeenCalled(); expect(navigateToLoginSpy).toHaveBeenCalled();
}); });
}); });

2
npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-actions.spec.ts

@ -107,7 +107,7 @@ describe('EntityAction', () => {
expect(action.text).toBe(options.text); expect(action.text).toBe(options.text);
expect(action.action).toBe(options.action); expect(action.action).toBe(options.action);
expect(action.permission).toBeUndefined(); expect(action.permission).toBe('');
expect(action.visible(null)).toBe(true); expect(action.visible(null)).toBe(true);
expect(action.icon).toBe(''); expect(action.icon).toBe('');
}); });

4
npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-props.spec.ts

@ -111,13 +111,11 @@ describe('EntityProp', () => {
type: ePropType.String, type: ePropType.String,
name: 'NAME', name: 'NAME',
}; };
const prop = new EntityProp(options); const prop = new EntityProp(options);
expect(prop.type).toBe(options.type); expect(prop.type).toBe(options.type);
expect(prop.name).toBe(options.name); expect(prop.name).toBe(options.name);
expect(prop.displayName).toBe(options.name); expect(prop.displayName).toBe(options.name);
expect(prop.permission).toBeUndefined(); expect(prop.permission).toBe('');
expect(prop.visible()).toBe(true); expect(prop.visible()).toBe(true);
expect(prop.sortable).toBe(false); expect(prop.sortable).toBe(false);
expect(prop.columnWidth).toBeUndefined(); expect(prop.columnWidth).toBeUndefined();

35
npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts

@ -1,4 +1,4 @@
import { ConfigStateService, LocalizationService } from '@abp/ng.core'; import { ConfigStateService, ExtensionEnumFieldDto, LocalizationService } from '@abp/ng.core';
import { BehaviorSubject, of } from 'rxjs'; import { BehaviorSubject, of } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { PropData } from '../lib/models/props'; import { PropData } from '../lib/models/props';
@ -11,10 +11,10 @@ const mockSessionState = {
onLanguageChange$: () => new BehaviorSubject('tr'), onLanguageChange$: () => new BehaviorSubject('tr'),
} as any; } as any;
const fields = [ const fields: ExtensionEnumFieldDto[] = [
{ name: 'foo', value: 1 }, { name: 'foo', value: {number: 1} },
{ name: 'bar', value: 2 }, { name: 'bar', value: {number: 2} },
{ name: 'baz', value: 3 }, { name: 'baz', value: {number: 3} },
]; ];
class MockPropData<R = any> extends PropData<R> { class MockPropData<R = any> extends PropData<R> {
@ -42,17 +42,13 @@ describe('Enum Utils', () => {
describe('#createEnum', () => { describe('#createEnum', () => {
const enumFromFields = createEnum(fields); const enumFromFields = createEnum(fields);
test.each` test.each([
key | expected {name:'foo', value: 'number', expected: 1},
${'foo'} | ${1} {name:'bar', value: 'number', expected: 2},
${'bar'} | ${2} {name:'baz', value: 'number', expected: 3}
${'baz'} | ${3} ])('should create an enum that returns $expected when $name $value is accessed',({name, value, expected})=>{
${1} | ${'foo'} expect(enumFromFields[name][value]).toBe(expected);
${2} | ${'bar'} })
${3} | ${'baz'}
`('should create an enum that returns $expected when $key is accessed', ({ key, expected }) => {
expect(enumFromFields[key]).toBe(expected);
});
}); });
describe('#createEnumValueResolver', () => { describe('#createEnumValueResolver', () => {
@ -75,7 +71,7 @@ describe('Enum Utils', () => {
'EnumProp', 'EnumProp',
); );
const propData = new MockPropData({ const propData = new MockPropData({
extraProperties: { EnumProp: value }, extraProperties: { EnumProp: value },
}); });
propData.getInjected = () => service as any; propData.getInjected = () => service as any;
@ -111,8 +107,9 @@ describe('Enum Utils', () => {
function createMockLocalizationService() { function createMockLocalizationService() {
const fakeAppConfigService = { get: () => of({ localization: mockL10n }) } as any; const fakeAppConfigService = { get: () => of({ localization: mockL10n }) } as any;
const configState = new ConfigStateService(fakeAppConfigService); const fakeLocalizationService = { get: () => of({ localization: mockL10n }) } as any;
const configState = new ConfigStateService(fakeAppConfigService, fakeLocalizationService, false);
configState.refreshAppState(); configState.refreshAppState();
return new LocalizationService(mockSessionState, null, null, configState); return new LocalizationService(mockSessionState, null, null, configState);
} }

6
npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.spec.ts

@ -127,14 +127,14 @@ describe('FormProp', () => {
expect(prop.type).toBe(options.type); expect(prop.type).toBe(options.type);
expect(prop.name).toBe(options.name); expect(prop.name).toBe(options.name);
expect(prop.displayName).toBe(options.name); expect(prop.displayName).toBe(options.name);
expect(prop.permission).toBeUndefined(); expect(prop.permission).toBe('');
expect(prop.visible()).toBe(true); expect(prop.visible()).toBe(true);
expect(prop.asyncValidators(null)).toEqual([]); expect(prop.asyncValidators(null)).toEqual([]);
expect(prop.validators(null)).toEqual([]); expect(prop.validators(null)).toEqual([]);
expect(prop.disabled()).toBe(false); expect(prop.disabled()).toBe(false);
expect(prop.readonly()).toBe(false); expect(prop.readonly()).toBe(false);
expect(prop.autocomplete).toBe('off'); expect(prop.autocomplete).toBe('off');
expect(prop.defaultValue).toBeNull(); expect(prop.defaultValue).toBe('');
expect(prop.options).toBeUndefined(); expect(prop.options).toBeUndefined();
expect(prop.id).toBe(options.name); expect(prop.id).toBe(options.name);
}); });
@ -144,7 +144,7 @@ describe('FormProp', () => {
${0} | ${0} ${0} | ${0}
${''} | ${''} ${''} | ${''}
${false} | ${false} ${false} | ${false}
${undefined} | ${null} ${undefined} | ${''}
`( `(
'should set defaultValue as $expected when $defaultValue is given', 'should set defaultValue as $expected when $defaultValue is given',
({ defaultValue, expected }) => { ({ defaultValue, expected }) => {

11
npm/ng-packs/packages/theme-shared/extensions/src/tests/localization.util.spec.ts

@ -3,17 +3,20 @@ import { createDisplayNameLocalizationPipeKeyGenerator } from '../lib/utils/loca
describe('Localization Utils', () => { describe('Localization Utils', () => {
describe('#createDisplayNameLocalizationPipeKeyGenerator', () => { describe('#createDisplayNameLocalizationPipeKeyGenerator', () => {
const generateDisplayName = createDisplayNameLocalizationPipeKeyGenerator({ const localization: ApplicationLocalizationConfigurationDto = {
values: { values:{
Foo: { Bar: 'Bar', 'DisplayName:Bar': 'Bar' }, Foo: { Bar: 'Bar', 'DisplayName:Bar': 'Bar' },
Default: { Bar: 'Bar', 'DisplayName:Bar': 'Bar' }, Default: { Bar: 'Bar', 'DisplayName:Bar': 'Bar' },
}, },
defaultResourceName: 'Default', defaultResourceName: 'Default',
currentCulture: null, resources: {},
languages: [], languages: [],
languageFilesMap: null, languageFilesMap: null,
languagesMap: null, languagesMap: null,
} as ApplicationLocalizationConfigurationDto); currentCulture: null
}
const generateDisplayName = createDisplayNameLocalizationPipeKeyGenerator(localization);
test.each` test.each`
displayName | fallback | expected displayName | fallback | expected

5
npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts

@ -11,7 +11,8 @@ import {
} from '../lib/utils/state.util'; } from '../lib/utils/state.util';
const fakeAppConfigService = { get: () => of(createMockState()) } as any; const fakeAppConfigService = { get: () => of(createMockState()) } as any;
const configState = new ConfigStateService(fakeAppConfigService); const fakeLocalizationService = { get: () => of(createMockState()) } as any;
const configState = new ConfigStateService(fakeAppConfigService,fakeLocalizationService,false);
configState.refreshAppState(); configState.refreshAppState();
describe('State Utils', () => { describe('State Utils', () => {
@ -30,7 +31,7 @@ describe('State Utils', () => {
}); });
it('should not emit when object extensions do not exist', done => { it('should not emit when object extensions do not exist', done => {
const emptyConfigState = new ConfigStateService(null); const emptyConfigState = new ConfigStateService(null,null,false);
const emit = jest.fn(); const emit = jest.fn();
getObjectExtensionEntitiesFromStore(emptyConfigState, 'Identity').subscribe(emit); getObjectExtensionEntitiesFromStore(emptyConfigState, 'Identity').subscribe(emit);

5
npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts

@ -5,7 +5,7 @@ import {
RouterOutletComponent, RouterOutletComponent,
RoutesService, RoutesService,
} from '@abp/ng.core'; } from '@abp/ng.core';
import { HttpClient } from '@angular/common/http'; import { HttpClient, HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
// eslint-disable-next-line @nx/enforce-module-boundaries // eslint-disable-next-line @nx/enforce-module-boundaries
@ -34,7 +34,7 @@ describe('BreadcrumbComponent', () => {
}, },
], ],
declarations: [LocalizationPipe, BreadcrumbComponent, BreadcrumbItemsComponent], declarations: [LocalizationPipe, BreadcrumbComponent, BreadcrumbItemsComponent],
imports: [RouterModule], imports: [RouterModule,HttpClientModule],
routes: [ routes: [
{ {
path: '', path: '',
@ -62,7 +62,6 @@ describe('BreadcrumbComponent', () => {
routes.add(mockRoutes); routes.add(mockRoutes);
await spectator.router.navigateByUrl('/identity/users'); await spectator.router.navigateByUrl('/identity/users');
spectator.detectChanges(); spectator.detectChanges();
const elements = spectator.queryAll('li'); const elements = spectator.queryAll('li');
expect(elements).toHaveLength(3); expect(elements).toHaveLength(3);
expect(elements[1]).toHaveText('Identity'); expect(elements[1]).toHaveText('Identity');

1
npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts

@ -10,7 +10,6 @@ import { CONFIRMATION_ICONS, DEFAULT_CONFIRMATION_ICONS } from '../tokens/confir
@NgModule({ @NgModule({
exports: [ConfirmationComponent], exports: [ConfirmationComponent],
entryComponents: [ConfirmationComponent],
declarations: [ConfirmationComponent], declarations: [ConfirmationComponent],
imports: [CoreTestingModule.withConfig()], imports: [CoreTestingModule.withConfig()],
providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }], providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }],

3
npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts

@ -1,5 +1,5 @@
import { CORE_OPTIONS, LocalizationPipe } from '@abp/ng.core'; import { CORE_OPTIONS, LocalizationPipe } from '@abp/ng.core';
import { HttpClient } from '@angular/common/http'; import { HttpClient, HttpClientModule } from '@angular/common/http';
import { ElementRef, Renderer2 } from '@angular/core'; import { ElementRef, Renderer2 } from '@angular/core';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -19,6 +19,7 @@ describe('ErrorComponent', () => {
useValue: { nativeElement: document.createElement('div') }, useValue: { nativeElement: document.createElement('div') },
}, },
], ],
imports:[HttpClientModule]
}); });
beforeEach(() => { beforeEach(() => {

1
npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts

@ -8,7 +8,6 @@ import { ToasterService } from '../services/toaster.service';
@NgModule({ @NgModule({
exports: [ToastContainerComponent], exports: [ToastContainerComponent],
entryComponents: [ToastContainerComponent],
declarations: [ToastContainerComponent, ToastComponent], declarations: [ToastContainerComponent, ToastComponent],
imports: [CoreTestingModule.withConfig()], imports: [CoreTestingModule.withConfig()],
}) })

45
templates/app-nolayers/aspnet-core/README.md

@ -1,18 +1,49 @@
## About this solution # MyCompanyName.MyProjectName
## About This Solution
This is a minimalist, non-layered startup solution with the ABP Framework. All the fundamental ABP modules are already installed. This is a minimalist, non-layered startup solution with the ABP Framework. All the fundamental ABP modules are already installed.
## How to run ## Before Running the Solution
### Generating a Signing Certificate
In the production environment, you need to use a production signing certificate. ABP Framework sets up signing and encryption certificates in your application and expects an `openiddict.pfx` file in your application.
This certificate is already generated by ABP CLI, so most of the time you don't need to generate it yourself. However, if you need to generate a certificate, you can use the following command:
```bash
dotnet dev-certs https -v -ep openiddict.pfx -p 00000000-0000-0000-0000-000000000000
```
> `00000000-0000-0000-0000-000000000000` is the password of the certificate, you can change it to any password you want.
It is recommended to use **two** RSA certificates, distinct from the certificate(s) used for HTTPS: one for encryption, one for signing.
For more information, please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-certificate-recommended-for-production-ready-scenarios
The application needs to connect to a database. Run the following command in the `MyCompanyName.MyProjectName` directory: > Also, see the [Configuring OpenIddict](https://docs.abp.io/en/abp/latest/Deployment/Configuring-OpenIddict#production-environment) documentation for more information.
### Install Client-Side Libraries
Run the following command in the directory of your final application:
```bash
abp install-libs
```
> This command installs all NPM packages for MVC/Razor Pages and Blazor Server UIs and this command is already run by the ABP CLI, so most of the time you don't need to run this command manually.
## How to Run
The application needs to connect to a database. Run the following command in the `MyCompanyName.MyProjectName` directory to migrate the database and seed the initial data:
````bash ````bash
dotnet run --migrate-database dotnet run --migrate-database
```` ````
This will create and seed the initial database. Then you can run the application with any IDE that supports .NET. This command will create and seed the initial database. Then you can run the application with any IDE that supports .NET.
Happy coding..!
## Deploying the Application
Deploying an ABP application is not different than deploying any .NET or ASP.NET Core application. However, there are some topics that you should care about when you are deploying your applications. You can check ABP's [Deployment documentation](https://docs.abp.io/en/abp/latest/Deployment/Index) before deploying your application.

39
templates/app/aspnet-core/README.md

@ -0,0 +1,39 @@
# MyCompanyName.MyProjectName
## About This Solution
This is a layered startup solution based on [Domain Driven Design (DDD)](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) practises. All the fundamental ABP modules are already installed. Check the [Application Startup Template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) documentation for more info.
## Before Running the Solution
### Generating a Signing Certificate
In the production environment, you need to use a production signing certificate. ABP Framework sets up signing and encryption certificates in your application and expects an `openiddict.pfx` file in your application.
This certificate is already generated by ABP CLI, so most of the time you don't need to generate it yourself. However, if you need to generate a certificate, you can use the following command:
```bash
dotnet dev-certs https -v -ep openiddict.pfx -p 00000000-0000-0000-0000-000000000000
```
> `00000000-0000-0000-0000-000000000000` is the password of the certificate, you can change it to any password you want.
It is recommended to use **two** RSA certificates, distinct from the certificate(s) used for HTTPS: one for encryption, one for signing.
For more information, please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-certificate-recommended-for-production-ready-scenarios
> Also, see the [Configuring OpenIddict](https://docs.abp.io/en/abp/latest/Deployment/Configuring-OpenIddict#production-environment) documentation for more information.
### Install Client-Side Libraries
Run the following command in the directory of your final application:
```bash
abp install-libs
```
> This command installs all NPM packages for MVC/Razor Pages and Blazor Server UIs and this command is already run by the ABP CLI, so most of the time you don't need to run this command manually.
## Deploying the Application
Deploying an ABP application is not different than deploying any .NET or ASP.NET Core application. However, there are some topics that you should care about when you are deploying your applications. You can check ABP's [Deployment documentation](https://docs.abp.io/en/abp/latest/Deployment/Index) before deploying your application.

8
templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs

@ -7,13 +7,11 @@ namespace MyCompanyName.MyProjectName;
public class MyProjectNameHostedService : IHostedService public class MyProjectNameHostedService : IHostedService
{ {
private readonly IAbpApplicationWithExternalServiceProvider _abpApplication;
private readonly HelloWorldService _helloWorldService; private readonly HelloWorldService _helloWorldService;
public MyProjectNameHostedService(HelloWorldService helloWorldService, IAbpApplicationWithExternalServiceProvider abpApplication) public MyProjectNameHostedService(HelloWorldService helloWorldService)
{ {
_helloWorldService = helloWorldService; _helloWorldService = helloWorldService;
_abpApplication = abpApplication;
} }
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
@ -21,8 +19,8 @@ public class MyProjectNameHostedService : IHostedService
await _helloWorldService.SayHelloAsync(); await _helloWorldService.SayHelloAsync();
} }
public async Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
await _abpApplication.ShutdownAsync(); return Task.CompletedTask;
} }
} }

23
templates/console/src/MyCompanyName.MyProjectName/Program.cs

@ -1,7 +1,9 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using Volo.Abp; using Volo.Abp;
@ -28,19 +30,20 @@ public class Program
{ {
Log.Information("Starting console host."); Log.Information("Starting console host.");
var builder = Host.CreateDefaultBuilder(args); var builder = Host.CreateApplicationBuilder(args);
builder.ConfigureServices(services => builder.Configuration.AddAppSettingsSecretsJson();
{ builder.Logging.ClearProviders().AddSerilog();
services.AddHostedService<MyProjectNameHostedService>();
services.AddApplicationAsync<MyProjectNameModule>(options => builder.ConfigureContainer(builder.Services.AddAutofacServiceProviderFactory());
{
options.Services.ReplaceConfiguration(services.GetConfiguration()); builder.Services.AddHostedService<MyProjectNameHostedService>();
});
}).AddAppSettingsSecretsJson().UseSerilog().UseAutofac().UseConsoleLifetime(); await builder.Services.AddApplicationAsync<MyProjectNameModule>();
var host = builder.Build(); var host = builder.Build();
await host.Services.GetRequiredService<IAbpApplicationWithExternalServiceProvider>().InitializeAsync(host.Services);
await host.InitializeAsync();
await host.RunAsync(); await host.RunAsync();

10
templates/console/src/MyCompanyName.MyProjectName/Properties/launchSettings.json

@ -0,0 +1,10 @@
{
"profiles": {
"MyCompanyName.MyProjectName": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
Loading…
Cancel
Save