diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/POST.md b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/POST.md new file mode 100644 index 0000000000..604d4c5c6c --- /dev/null +++ b/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) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blog-detail.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blog-detail.jpg new file mode 100644 index 0000000000..d1e2a06f94 Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blog-detail.jpg differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blogs.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blogs.jpg new file mode 100644 index 0000000000..70d276349c Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blogs.jpg differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/cmskit-module-features.png b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/cmskit-module-features.png new file mode 100644 index 0000000000..b6dd409e0a Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/cmskit-module-features.png differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/homepage.png b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/homepage.png new file mode 100644 index 0000000000..3c485415aa Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/homepage.png differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery-detail.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery-detail.jpg new file mode 100644 index 0000000000..70ea35ea2d Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery-detail.jpg differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery.jpg new file mode 100644 index 0000000000..b8b6291342 Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery.jpg differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/menu-admin-side.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/menu-admin-side.jpg new file mode 100644 index 0000000000..c4dd12275e Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/menu-admin-side.jpg differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/pages-admin-side.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/pages-admin-side.jpg new file mode 100644 index 0000000000..27ba597e13 Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/pages-admin-side.jpg differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/products-abp-commercial.png b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/products-abp-commercial.png new file mode 100644 index 0000000000..0401a947f5 Binary files /dev/null and b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/products-abp-commercial.png differ diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index 206c2c58a4..d8afe5c21c 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -1,12 +1,12 @@ # 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. ## 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# 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. diff --git a/docs/en/Deployment/Configuring-OpenIddict.md b/docs/en/Deployment/Configuring-OpenIddict.md index bf7bc9c619..b044bcb275 100644 --- a/docs/en/Deployment/Configuring-OpenIddict.md +++ b/docs/en/Deployment/Configuring-OpenIddict.md @@ -2,61 +2,46 @@ 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 public override void PreConfigureServices(ServiceConfigurationContext context) { var hostingEnvironment = context.Services.GetHostingEnvironment(); - // Development environment - if (hostingEnvironment.IsDevelopment()) - { - PreConfigure(options => - { - // This is default value, you can remove this line. - options.AddDevelopmentEncryptionAndSigningCertificate = true; - }); - } - - // Production or Staging environment if (!hostingEnvironment.IsDevelopment()) { - PreConfigure(options => - { - options.AddDevelopmentEncryptionAndSigningCertificate = false; - }); - - PreConfigure(builder => - { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment)); - - //... - }); + PreConfigure(options => + { + options.AddDevelopmentEncryptionAndSigningCertificate = false; + }); + + PreConfigure(serverBuilder => + { + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", "00000000-0000-0000-0000-000000000000"); + }); } } - -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv) -{ - return new X509Certificate2(Path.Combine(hostingEnv.ContentRootPath, "authserver.pfx"), "00000000-0000-0000-0000-000000000000"); -} ```` ## 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)). -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 -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. -> 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 diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/AbpConfigurationExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/AbpConfigurationExtensions.cs new file mode 100644 index 0000000000..6e0a14fd94 --- /dev/null +++ b/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); + } +} diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostExtensions.cs new file mode 100644 index 0000000000..12c03e14f5 --- /dev/null +++ b/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(); + var applicationLifetime = host.Services.GetRequiredService(); + + applicationLifetime.ApplicationStopping.Register(() => AsyncHelper.RunSync(() => application.ShutdownAsync())); + applicationLifetime.ApplicationStopped.Register(() => application.Dispose()); + + await application.InitializeAsync(host.Services); + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueComparers/ExtraPropertyDictionaryValueComparer.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueComparers/ExtraPropertyDictionaryValueComparer.cs index dca9ee0fc0..27701d196d 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueComparers/ExtraPropertyDictionaryValueComparer.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueComparers/ExtraPropertyDictionaryValueComparer.cs @@ -9,9 +9,29 @@ public class ExtraPropertyDictionaryValueComparer : ValueComparer d1!.SequenceEqual(d2!), - d => d.Aggregate(0, (k, v) => HashCode.Combine(k, v.GetHashCode())), - d => new ExtraPropertyDictionary(d)) + (a, b) => Compare(a, b), + d => d.Aggregate(0, (k, v) => HashCode.Combine(k, v.GetHashCode())), + 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!); + } } 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 dc14ee4203..c0dfeabbd1 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,15 @@ -using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Castle; +using Volo.Abp.Data; using Volo.Abp.EventBus; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; using Volo.Abp.Validation; 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.RemoteServices; @@ -27,5 +31,11 @@ public class AbpHttpClientModule : AbpModule { context.Services.AddHttpClient(); context.Services.AddTransient(typeof(DynamicHttpProxyInterceptorClientProxy<>)); + + Configure(options => + { + options.QueryStringConverts.Add(typeof(ExtraPropertyDictionary), typeof(ExtraPropertyDictionaryToQueryString)); + options.FormDataConverts.Add(typeof(ExtraPropertyDictionary), typeof(ExtraPropertyDictionaryToFormData)); + }); } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ExtraPropertyDictionaryConverts/ExtraPropertyDictionaryToFormData.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ExtraPropertyDictionaryConverts/ExtraPropertyDictionaryToFormData.cs new file mode 100644 index 0000000000..72cfb55156 --- /dev/null +++ b/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, ITransientDependency +{ + public Task>> ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, ExtraPropertyDictionary extraPropertyDictionary) + { + if (extraPropertyDictionary.IsNullOrEmpty()) + { + return Task.FromResult>>(null!); + } + + var formDataContents = new List>(); + foreach (var item in extraPropertyDictionary) + { + formDataContents.Add(new KeyValuePair($"ExtraProperties[{item.Key}]", new StringContent(item.Value!.ToString()!, Encoding.UTF8))); + } + + return Task.FromResult(formDataContents); + } +} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ExtraPropertyDictionaryConverts/ExtraPropertyDictionaryToQueryString.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ExtraPropertyDictionaryConverts/ExtraPropertyDictionaryToQueryString.cs new file mode 100644 index 0000000000..a1b2919615 --- /dev/null +++ b/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, ITransientDependency +{ + public Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, ExtraPropertyDictionary extraPropertyDictionary) + { + if (extraPropertyDictionary.IsNullOrEmpty()) + { + return Task.FromResult(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()); + } +} diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index fbaf6b4d3e..7671dcf0cb 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/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.AspNetCore.Mvc.Conventions; using Volo.Abp.Content; +using Volo.Abp.Data; using Volo.Abp.Domain.Repositories; using Volo.Abp.Http.Client; using Volo.Abp.TestApp.Application; @@ -280,7 +281,7 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase [Fact] public async Task GetParamsFromQueryAsync() { - var result = await _peopleAppService.GetParamsFromQueryAsync(new GetParamsInput() + var input = new GetParamsInput() { NameValues = new List() { @@ -300,34 +301,39 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase Name = "name3", 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] public async Task GetParamsFromFormAsync() { - var result = await _peopleAppService.GetParamsFromFormAsync(new GetParamsInput() + var input = new GetParamsInput() { NameValues = new List() { - new GetParamsNameValue() - { - Name = "name1", - Value = "value1" - }, - new GetParamsNameValue() - { - Name = "name2", - Value = "value2" - } + new GetParamsNameValue() {Name = "name1", Value = "value1"}, + new GetParamsNameValue() {Name = "name2", Value = "value2"} }, NameValue = new GetParamsNameValue() { - Name = "name3", - Value = "value3" + Name = "name3", 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"); } } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs index 2c64773c88..9e3a5d28dd 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs @@ -23,6 +23,11 @@ public class TestObjectToFormData : IObjectToFormData>, { formDataContents.Add(new KeyValuePair($"NameValues[{i}].Name", new StringContent(values[i].Name, Encoding.UTF8))); formDataContents.Add(new KeyValuePair($"NameValues[{i}].Value", new StringContent(values[i].Value, Encoding.UTF8))); + + foreach (var item in values[i].ExtraProperties) + { + formDataContents.Add(new KeyValuePair($"NameValues[{i}].ExtraProperties[{item.Key}]", new StringContent(item.Value!.ToString()!, Encoding.UTF8))); + } } return Task.FromResult(formDataContents); diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs index d4ca6f7285..a27528cb77 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs @@ -22,6 +22,10 @@ public class TestObjectToQueryString : IObjectToQueryString NameValues { get; set; } public GetParamsNameValue NameValue { get; set; } } -public class GetParamsNameValue +public class GetParamsNameValue : ExtensibleObject { public string Name { get; set; } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index 752a548320..a7d64228bd 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -129,17 +129,19 @@ public class PeopleAppService : CrudAppService, IPeople public Task GetParamsFromQueryAsync([FromQuery] GetParamsInput input) { - return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + - input.NameValues?.FirstOrDefault()?.Value + ":" + + return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + input.NameValues?.FirstOrDefault()?.Value + ":" + + input.NameValues?.FirstOrDefault()?.ExtraProperties["TestPropertyInList"] + ":" + 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 GetParamsFromFormAsync([FromForm] GetParamsInput input) { - return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + - input.NameValues?.FirstOrDefault()?.Value + ":" + + return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + input.NameValues?.FirstOrDefault()?.Value + ":" + + input.NameValues?.FirstOrDefault()?.ExtraProperties["TestPropertyInList"] + ":" + input.NameValues?.LastOrDefault()?.Name + "-" + input.NameValues?.LastOrDefault()?.Value + ":" + - input.NameValue?.Name + "-" + input.NameValue?.Value); + input.NameValue?.Name + "-" + input.NameValue?.Value + ":" + + input.ExtraProperties["TestProperty"]); } } diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml index 1ffdf2595e..f146878b0f 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml @@ -31,10 +31,7 @@ diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs index b30a2308e1..e1521d32c0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs +++ b/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)] public virtual async Task> 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); return new PagedResultDto( diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs index 3621e5d564..b17e595442 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs @@ -97,7 +97,7 @@ public class CmsKitAdminMenuContributor : IMenuContributor CmsKitAdminMenus.GlobalResources.GlobalResourcesMenu, l["GlobalResources"], "/Cms/GlobalResources", - "bi bi-code-slash", + "fa fa-code", order: 4) .RequireFeatures(CmsKitFeatures.GlobalResourceEnable) .RequireGlobalFeatures(typeof(GlobalResourcesFeature)) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagRepository.cs index d00457b53a..f6210d16f8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagRepository.cs @@ -26,6 +26,9 @@ public interface ITagRepository : IBasicRepository Task> GetListAsync( string filter, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string sorting = null, CancellationToken cancellationToken = default); Task GetCountAsync( diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreTagRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreTagRepository.cs index 2791f69f6a..9899c228e2 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreTagRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreTagRepository.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Dynamic.Core; using System.Threading; using System.Threading.Tasks; using Volo.Abp; @@ -96,9 +97,16 @@ public class EfCoreTagRepository : EfCoreRepository .ToListAsync(cancellationToken: GetCancellationToken(cancellationToken)); } - public virtual async Task> GetListAsync(string filter, CancellationToken cancellationToken = default) + public virtual async Task> 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 GetCountAsync(string filter, CancellationToken cancellationToken = default) diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoTagRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoTagRepository.cs index d8f938c3f6..e8f702dbb2 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoTagRepository.cs +++ b/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.Collections.Generic; using System.Linq; +using System.Linq.Dynamic.Core; using System.Threading; using System.Threading.Tasks; using Volo.Abp; @@ -107,9 +108,17 @@ public class MongoTagRepository : MongoDbRepository> GetListAsync(string filter, CancellationToken cancellationToken = default) + public virtual async Task> 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>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetCountAsync(string filter, CancellationToken cancellationToken = default) diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs index c52902bfed..6f3eb540f2 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs @@ -188,11 +188,11 @@ public class EfCoreIdentityUserRepository : EfCoreRepository identityUser.Roles.Any(x => x.RoleId == roleId.Value)) .WhereIf(organizationUnitId.HasValue, identityUser => identityUser.OrganizationUnits.Any(x => x.OrganizationUnitId == organizationUnitId.Value)) - .WhereIf(!string.IsNullOrWhiteSpace(userName), x => x.UserName == userName) - .WhereIf(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber == phoneNumber) - .WhereIf(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email == emailAddress) - .WhereIf(!string.IsNullOrWhiteSpace(name), x => x.Name == name) - .WhereIf(!string.IsNullOrWhiteSpace(surname), x => x.Surname == surname) + .WhereIf(!string.IsNullOrWhiteSpace(userName), x => x.UserName.Contains(userName)) + .WhereIf(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber.Contains(phoneNumber)) + .WhereIf(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email.Contains(emailAddress)) + .WhereIf(!string.IsNullOrWhiteSpace(name), x => x.Name.Contains(name)) + .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(notActive.HasValue, x => x.IsActive == !notActive.Value) .WhereIf(emailConfirmed.HasValue, x => x.EmailConfirmed == emailConfirmed.Value) diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs index cd2e2af595..9a766ed1c9 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs @@ -40,7 +40,7 @@ public class EfCoreOrganizationUnitRepository { return await (await GetDbSetAsync()) .IncludeDetails(includeDetails) - .Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value) + .Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId) .ToListAsync(GetCancellationToken(cancellationToken)); } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs index 51960edbba..255e492c05 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs @@ -168,11 +168,11 @@ public class MongoIdentityUserRepository : MongoDbRepository>(roleId.HasValue, identityUser => identityUser.Roles.Any(x => x.RoleId == roleId.Value)) .WhereIf>(organizationUnitId.HasValue, identityUser => identityUser.OrganizationUnits.Any(x => x.OrganizationUnitId == organizationUnitId.Value)) - .WhereIf>(!string.IsNullOrWhiteSpace(userName), x => x.UserName == userName) - .WhereIf>(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber == phoneNumber) - .WhereIf>(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email == emailAddress) - .WhereIf>(!string.IsNullOrWhiteSpace(name), x => x.Name == name) - .WhereIf>(!string.IsNullOrWhiteSpace(surname), x => x.Surname == surname) + .WhereIf>(!string.IsNullOrWhiteSpace(userName), x => x.UserName.Contains(userName)) + .WhereIf>(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber.Contains(phoneNumber)) + .WhereIf>(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email.Contains(emailAddress)) + .WhereIf>(!string.IsNullOrWhiteSpace(name), x => x.Name.Contains(name)) + .WhereIf>(!string.IsNullOrWhiteSpace(surname), x => x.Surname.Contains(surname)) .WhereIf>(isLockedOut.HasValue && isLockedOut.Value, x => x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow) .WhereIf>(isLockedOut.HasValue && !isLockedOut.Value, x => !(x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow)) .WhereIf>(notActive.HasValue, x => x.IsActive == !notActive.Value) diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs index 23726693ad..728696ada8 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs @@ -39,7 +39,7 @@ public class MongoOrganizationUnitRepository CancellationToken cancellationToken = default) { 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)); } diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs index be757d5008..9ff41f2385 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs @@ -47,8 +47,13 @@ public abstract class OrganizationUnitRepository_Tests : AbpIden [Fact] public async Task GetAllChildrenWithParentCodeAsync() { - (await _organizationUnitRepository.GetAllChildrenWithParentCodeAsync(OrganizationUnit.CreateCode(0), - _guidGenerator.Create())).ShouldNotBeNull(); + var allChildren = await _organizationUnitRepository.GetAllChildrenWithParentCodeAsync(OrganizationUnit.CreateCode(0), _guidGenerator.Create()); + allChildren.ShouldNotBeNull(); + allChildren.ShouldBeEmpty(); + + allChildren = (await _organizationUnitRepository.GetAllChildrenWithParentCodeAsync(OrganizationUnit.CreateCode(1), null)); + allChildren.ShouldNotBeNull(); + allChildren.ShouldNotBeEmpty(); } [Fact] diff --git a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts index 30f9f92e73..34f9451b0a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts +++ b/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 @NgModule({ declarations: [TestComponent], - entryComponents: [TestComponent], }) class TestModule {} diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 2076be84b4..411a9e8041 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -36,7 +36,6 @@ const LAYOUTS = [ @NgModule({ imports: [RouterModule], declarations: [...LAYOUTS], - entryComponents: [...LAYOUTS], }) class DummyLayoutModule {} diff --git a/npm/ng-packs/packages/core/src/lib/tests/object-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/object-utils.spec.ts index 235d2e90ed..f2548bfc79 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/object-utils.spec.ts +++ b/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', () => { - const target = { a: 1 }; - const source = { b: 2 }; const expected = { a: 1, b: 2 }; - expect(deepMerge(target, source)).toEqual(expected); - expect(deepMerge(source, target)).toEqual(expected); + expect(deepMerge({ a: 1 }, { b: 2 })).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', () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts index ab9c6a76ae..b2ce154421 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts +++ b/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 { DummyInjector } from './utils/common.utils'; import { mockPermissionService } from './utils/permission-service.spec.utils'; +import { mockCompareFunction } from './utils/mock-compare-function'; const updateStream$ = new Subject(); - export const mockRoutesService = (injectorPayload = {} as { [key: string]: any }) => { const injector = new DummyInjector({ PermissionService: mockPermissionService(), ConfigStateService: { createOnUpdateStream: () => updateStream$ }, OTHERS_GROUP: 'OthersGroup', + SORT_COMPARE_FUNC: mockCompareFunction, ...injectorPayload, }); return new RoutesService(injector); @@ -50,7 +51,6 @@ describe('Routes Service', () => { const flat = await lastValueFrom(service.flat$.pipe(take(1))); const tree = await lastValueFrom(service.tree$.pipe(take(1))); const visible = await lastValueFrom(service.visible$.pipe(take(1))); - expect(flat.length).toBe(5); expect(flat[0].name).toBe('baz'); expect(flat[1].name).toBe('qux'); diff --git a/npm/ng-packs/packages/core/src/lib/tests/utils/mock-compare-function.ts b/npm/ng-packs/packages/core/src/lib/tests/utils/mock-compare-function.ts new file mode 100644 index 0000000000..d43adcc2f0 --- /dev/null +++ b/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 +} \ No newline at end of file diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts index 1b102b8589..04a7652300 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts +++ b/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 { OAuthService } from 'angular-oauth2-oidc'; 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', () => { - let spectator: SpectatorService; - let interceptor: ApiInterceptor; + let spectator: SpectatorService; + let interceptor: OAuthApiInterceptor; let oauthService: SpyObject; let sessionState: SpyObject; let httpWaitService: SpyObject; @@ -15,7 +16,7 @@ describe('ApiInterceptor', () => { const testTenantKey = 'TEST_TENANT_KEY'; const createService = createServiceFactory({ - service: ApiInterceptor, + service: OAuthApiInterceptor, mocks: [OAuthService, SessionStateService], providers: [{ provide: TENANT_KEY, useValue: testTenantKey }], }); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts index 1440f9249a..dbfb3c74f3 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts +++ b/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 { AbpOAuthGuard } from '../guards/oauth.guard'; import { AuthService } from '@abp/ng.core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; describe('AuthGuard', () => { let spectator: SpectatorService; - let guard: AbpOAuthGuard; + let guard : AbpOAuthGuard; + let route = createSpyObject(ActivatedRouteSnapshot) + let state = createSpyObject(RouterStateSnapshot) + const createService = createServiceFactory({ service: AbpOAuthGuard, mocks: [OAuthService, AuthService], @@ -18,7 +22,7 @@ describe('AuthGuard', () => { it('should return true when user logged in', () => { 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', () => { @@ -26,7 +30,7 @@ describe('AuthGuard', () => { spectator.inject(OAuthService).hasValidAccessToken.andReturn(false); const navigateToLoginSpy = jest.spyOn(authService, 'navigateToLogin'); - expect(guard.canActivate()).toBe(false); + expect(guard.canActivate(route, state)).toBe(false); expect(navigateToLoginSpy).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-actions.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-actions.spec.ts index 35833a1950..5ee3889c0c 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-actions.spec.ts +++ b/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.action).toBe(options.action); - expect(action.permission).toBeUndefined(); + expect(action.permission).toBe(''); expect(action.visible(null)).toBe(true); expect(action.icon).toBe(''); }); diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-props.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-props.spec.ts index 8de503c5f0..bb1c2c7cc4 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-props.spec.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/tests/entity-props.spec.ts @@ -111,13 +111,11 @@ describe('EntityProp', () => { type: ePropType.String, name: 'NAME', }; - const prop = new EntityProp(options); - expect(prop.type).toBe(options.type); expect(prop.name).toBe(options.name); expect(prop.displayName).toBe(options.name); - expect(prop.permission).toBeUndefined(); + expect(prop.permission).toBe(''); expect(prop.visible()).toBe(true); expect(prop.sortable).toBe(false); expect(prop.columnWidth).toBeUndefined(); 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 d0fbdde765..56c61e1fc8 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,4 +1,4 @@ -import { ConfigStateService, LocalizationService } from '@abp/ng.core'; +import { ConfigStateService, ExtensionEnumFieldDto, LocalizationService } from '@abp/ng.core'; import { BehaviorSubject, of } from 'rxjs'; import { take } from 'rxjs/operators'; import { PropData } from '../lib/models/props'; @@ -11,10 +11,10 @@ const mockSessionState = { onLanguageChange$: () => new BehaviorSubject('tr'), } as any; -const fields = [ - { name: 'foo', value: 1 }, - { name: 'bar', value: 2 }, - { name: 'baz', value: 3 }, +const fields: ExtensionEnumFieldDto[] = [ + { name: 'foo', value: {number: 1} }, + { name: 'bar', value: {number: 2} }, + { name: 'baz', value: {number: 3} }, ]; class MockPropData extends PropData { @@ -42,17 +42,13 @@ describe('Enum Utils', () => { describe('#createEnum', () => { const enumFromFields = createEnum(fields); - test.each` - key | expected - ${'foo'} | ${1} - ${'bar'} | ${2} - ${'baz'} | ${3} - ${1} | ${'foo'} - ${2} | ${'bar'} - ${3} | ${'baz'} - `('should create an enum that returns $expected when $key is accessed', ({ key, expected }) => { - expect(enumFromFields[key]).toBe(expected); - }); + test.each([ + {name:'foo', value: 'number', expected: 1}, + {name:'bar', value: 'number', expected: 2}, + {name:'baz', value: 'number', expected: 3} + ])('should create an enum that returns $expected when $name $value is accessed',({name, value, expected})=>{ + expect(enumFromFields[name][value]).toBe(expected); + }) }); describe('#createEnumValueResolver', () => { @@ -75,7 +71,7 @@ describe('Enum Utils', () => { 'EnumProp', ); const propData = new MockPropData({ - extraProperties: { EnumProp: value }, + extraProperties: { EnumProp: value }, }); propData.getInjected = () => service as any; @@ -111,8 +107,9 @@ describe('Enum Utils', () => { function createMockLocalizationService() { 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(); return new LocalizationService(mockSessionState, null, null, configState); -} +} \ No newline at end of file diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.spec.ts index bd933741a5..3b3298babc 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.spec.ts +++ b/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.name).toBe(options.name); expect(prop.displayName).toBe(options.name); - expect(prop.permission).toBeUndefined(); + expect(prop.permission).toBe(''); expect(prop.visible()).toBe(true); expect(prop.asyncValidators(null)).toEqual([]); expect(prop.validators(null)).toEqual([]); expect(prop.disabled()).toBe(false); expect(prop.readonly()).toBe(false); expect(prop.autocomplete).toBe('off'); - expect(prop.defaultValue).toBeNull(); + expect(prop.defaultValue).toBe(''); expect(prop.options).toBeUndefined(); expect(prop.id).toBe(options.name); }); @@ -144,7 +144,7 @@ describe('FormProp', () => { ${0} | ${0} ${''} | ${''} ${false} | ${false} - ${undefined} | ${null} + ${undefined} | ${''} `( 'should set defaultValue as $expected when $defaultValue is given', ({ defaultValue, expected }) => { diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/localization.util.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/localization.util.spec.ts index 007b304b42..da41ea913b 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/localization.util.spec.ts +++ b/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('#createDisplayNameLocalizationPipeKeyGenerator', () => { - const generateDisplayName = createDisplayNameLocalizationPipeKeyGenerator({ - values: { + const localization: ApplicationLocalizationConfigurationDto = { + values:{ Foo: { Bar: 'Bar', 'DisplayName:Bar': 'Bar' }, Default: { Bar: 'Bar', 'DisplayName:Bar': 'Bar' }, }, defaultResourceName: 'Default', - currentCulture: null, + resources: {}, languages: [], languageFilesMap: null, languagesMap: null, - } as ApplicationLocalizationConfigurationDto); + currentCulture: null + } + + const generateDisplayName = createDisplayNameLocalizationPipeKeyGenerator(localization); test.each` displayName | fallback | expected diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts index cedd632165..c9efdce913 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts @@ -11,7 +11,8 @@ import { } from '../lib/utils/state.util'; 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(); describe('State Utils', () => { @@ -30,7 +31,7 @@ describe('State Utils', () => { }); 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(); getObjectExtensionEntitiesFromStore(emptyConfigState, 'Identity').subscribe(emit); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts index 75f0c8ab55..40a1109de0 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts @@ -5,7 +5,7 @@ import { RouterOutletComponent, RoutesService, } from '@abp/ng.core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; import { RouterModule } from '@angular/router'; import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; // eslint-disable-next-line @nx/enforce-module-boundaries @@ -34,7 +34,7 @@ describe('BreadcrumbComponent', () => { }, ], declarations: [LocalizationPipe, BreadcrumbComponent, BreadcrumbItemsComponent], - imports: [RouterModule], + imports: [RouterModule,HttpClientModule], routes: [ { path: '', @@ -62,7 +62,6 @@ describe('BreadcrumbComponent', () => { routes.add(mockRoutes); await spectator.router.navigateByUrl('/identity/users'); spectator.detectChanges(); - const elements = spectator.queryAll('li'); expect(elements).toHaveLength(3); expect(elements[1]).toHaveText('Identity'); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts index 5674a501de..587365e3f6 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts +++ b/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({ exports: [ConfirmationComponent], - entryComponents: [ConfirmationComponent], declarations: [ConfirmationComponent], imports: [CoreTestingModule.withConfig()], providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }], diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts index 510ef4104d..5bde4d2a54 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts +++ b/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 { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; import { ElementRef, Renderer2 } from '@angular/core'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; import { Subject } from 'rxjs'; @@ -19,6 +19,7 @@ describe('ErrorComponent', () => { useValue: { nativeElement: document.createElement('div') }, }, ], + imports:[HttpClientModule] }); beforeEach(() => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts index d0d41edf22..4e1b2830c7 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts @@ -8,7 +8,6 @@ import { ToasterService } from '../services/toaster.service'; @NgModule({ exports: [ToastContainerComponent], - entryComponents: [ToastContainerComponent], declarations: [ToastContainerComponent, ToastComponent], imports: [CoreTestingModule.withConfig()], }) diff --git a/templates/app-nolayers/aspnet-core/README.md b/templates/app-nolayers/aspnet-core/README.md index 9e142b3119..c0b1a5e76d 100644 --- a/templates/app-nolayers/aspnet-core/README.md +++ b/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. -## 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 dotnet run --migrate-database ```` -This will create and seed the initial database. Then you can run the application with any IDE that supports .NET. - -Happy coding..! - +This command will create and seed the initial database. Then you can run the application with any IDE that supports .NET. +## 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. diff --git a/templates/app/aspnet-core/README.md b/templates/app/aspnet-core/README.md new file mode 100644 index 0000000000..680579f70b --- /dev/null +++ b/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. \ No newline at end of file diff --git a/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs b/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs index c31bcc282f..3876cac7e2 100644 --- a/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs +++ b/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs @@ -7,13 +7,11 @@ namespace MyCompanyName.MyProjectName; public class MyProjectNameHostedService : IHostedService { - private readonly IAbpApplicationWithExternalServiceProvider _abpApplication; private readonly HelloWorldService _helloWorldService; - public MyProjectNameHostedService(HelloWorldService helloWorldService, IAbpApplicationWithExternalServiceProvider abpApplication) + public MyProjectNameHostedService(HelloWorldService helloWorldService) { _helloWorldService = helloWorldService; - _abpApplication = abpApplication; } public async Task StartAsync(CancellationToken cancellationToken) @@ -21,8 +19,8 @@ public class MyProjectNameHostedService : IHostedService await _helloWorldService.SayHelloAsync(); } - public async Task StopAsync(CancellationToken cancellationToken) + public Task StopAsync(CancellationToken cancellationToken) { - await _abpApplication.ShutdownAsync(); + return Task.CompletedTask; } } diff --git a/templates/console/src/MyCompanyName.MyProjectName/Program.cs b/templates/console/src/MyCompanyName.MyProjectName/Program.cs index 79d12b850f..dd1ce382c9 100644 --- a/templates/console/src/MyCompanyName.MyProjectName/Program.cs +++ b/templates/console/src/MyCompanyName.MyProjectName/Program.cs @@ -1,7 +1,9 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Serilog; using Serilog.Events; using Volo.Abp; @@ -28,19 +30,20 @@ public class Program { Log.Information("Starting console host."); - var builder = Host.CreateDefaultBuilder(args); + var builder = Host.CreateApplicationBuilder(args); - builder.ConfigureServices(services => - { - services.AddHostedService(); - services.AddApplicationAsync(options => - { - options.Services.ReplaceConfiguration(services.GetConfiguration()); - }); - }).AddAppSettingsSecretsJson().UseSerilog().UseAutofac().UseConsoleLifetime(); + builder.Configuration.AddAppSettingsSecretsJson(); + builder.Logging.ClearProviders().AddSerilog(); + + builder.ConfigureContainer(builder.Services.AddAutofacServiceProviderFactory()); + + builder.Services.AddHostedService(); + + await builder.Services.AddApplicationAsync(); var host = builder.Build(); - await host.Services.GetRequiredService().InitializeAsync(host.Services); + + await host.InitializeAsync(); await host.RunAsync(); diff --git a/templates/console/src/MyCompanyName.MyProjectName/Properties/launchSettings.json b/templates/console/src/MyCompanyName.MyProjectName/Properties/launchSettings.json new file mode 100644 index 0000000000..99027a4bbc --- /dev/null +++ b/templates/console/src/MyCompanyName.MyProjectName/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "MyCompanyName.MyProjectName": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file