+```
+
+You can check this Git commit for details.
+
+https://github.com/abpframework/abp-samples/commit/f3014e0ec422cb2d8816d0e00dd6ab9cc1adfc21
diff --git a/docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/new.gif b/docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/new.gif
new file mode 100644
index 0000000000..391963011c
Binary files /dev/null and b/docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/new.gif differ
diff --git a/docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/old.gif b/docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/old.gif
new file mode 100644
index 0000000000..a84714d4ee
Binary files /dev/null and b/docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/old.gif differ
diff --git a/docs/en/Customizing-Application-Modules-Extending-Entities.md b/docs/en/Customizing-Application-Modules-Extending-Entities.md
index a3c9ae4e9a..c2cacb4e4c 100644
--- a/docs/en/Customizing-Application-Modules-Extending-Entities.md
+++ b/docs/en/Customizing-Application-Modules-Extending-Entities.md
@@ -101,7 +101,7 @@ public class MyLocalIdentityUserChangeEventHandler :
````
* `EntityChangedEventData` covers create, update and delete events for the given entity. If you need, you can subscribe to create, update and delete events individually (in the same class or different classes).
-* This code will be executed **out of the local transaction**, because it listens the `EntityChanged` event. You can subscribe to the `EntityChangingEventData` to perform your event handler in **the same local (in-process) transaction** if the current [unit of work](Unit-Of-Work.md) is transactional.
+* This code will be executed in the **current unit of work**, the whole process becomes transactional.
> Reminder: This approach needs to change the `IdentityUser` entity in the same process contains the handler class. It perfectly works even for a clustered environment (when multiple instances of the same application are running on multiple servers).
diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md
index bfce8a71bf..206c2c58a4 100644
--- a/docs/en/Dependency-Injection.md
+++ b/docs/en/Dependency-Injection.md
@@ -2,7 +2,7 @@
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.
-> While ABP has no core dependency to any 3rd-party DI provider, 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 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
@@ -20,24 +20,24 @@ public class BlogModule : AbpModule
## Conventional Registration
-ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` by overriding the `PreConfigureServices` method.
+ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` in the constructor of your module class. Example:
````C#
public class BlogModule : AbpModule
{
- public override void PreConfigureServices(ServiceConfigurationContext context)
+ public BlogModule()
{
SkipAutoServiceRegistration = true;
}
}
````
-Once you skip auto registration, you should manually register your services. In that case, ``AddAssemblyOf`` extension method can help you to register all your services by convention. Example:
+Once you skip the auto registration, you should manually register your services. In that case, ``AddAssemblyOf`` extension method can help you to register all your services by convention. Example:
````c#
public class BlogModule : AbpModule
{
- public override void PreConfigureServices(ServiceConfigurationContext context)
+ public BlogModule()
{
SkipAutoServiceRegistration = true;
}
@@ -105,9 +105,7 @@ Example:
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
public class TaxCalculator
{
-
}
-
````
``Dependency`` attribute has a higher priority than other dependency interfaces if it defines the ``Lifetime`` property.
@@ -120,7 +118,6 @@ public class TaxCalculator
[ExposeServices(typeof(ITaxCalculator))]
public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
{
-
}
````
@@ -179,7 +176,11 @@ public class MyModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
//Replacing the IConnectionStringResolver service
- context.Services.Replace(ServiceDescriptor.Transient());
+ context.Services.Replace(
+ ServiceDescriptor.Transient<
+ IConnectionStringResolver,
+ MyConnectionStringResolver
+ >());
}
}
````
@@ -202,7 +203,7 @@ public class TaxAppService : ApplicationService
_taxCalculator = taxCalculator;
}
- public void DoSomething()
+ public async Task DoSomethingAsync()
{
//...use _taxCalculator...
}
@@ -227,7 +228,7 @@ public class MyService : ITransientDependency
Logger = NullLogger.Instance;
}
- public void DoSomething()
+ public async Task DoSomethingAsync()
{
//...use Logger to write logs...
}
@@ -244,17 +245,21 @@ One restriction of property injection is that you cannot use the dependency in y
Property injection is also useful when you want to design a base class that has some common services injected by default. If you're going to use constructor injection, all derived classes should also inject depended services into their own constructors which makes development harder. However, be very careful using property injection for non-optional services as it makes it harder to clearly see the requirements of a class.
-#### DisablePropertyInjectionAttribute
+#### DisablePropertyInjection Attribute
-You can use `[DisablePropertyInjection]` attribute on class or properties to disable property injection for the whole class or some specific properties.
+You can use `[DisablePropertyInjection]` attribute on classes or their properties to disable property injection for the whole class or some specific properties.
````C#
+// Disabling for all properties of the MyService class
[DisablePropertyInjection]
public class MyService : ITransientDependency
{
+ public ILogger Logger { get; set; }
+
public ITaxCalculator TaxCalculator { get; set; }
}
+// Disabling only for the TaxCalculator property
public class MyService : ITransientDependency
{
public ILogger Logger { get; set; }
@@ -262,17 +267,21 @@ public class MyService : ITransientDependency
[DisablePropertyInjection]
public ITaxCalculator TaxCalculator { get; set; }
}
-
````
### Resolve Service from IServiceProvider
-You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject IServiceProvider into your class and use ``GetService`` method as shown below:
+You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject `IServiceProvider` into your class and use the ``GetService`` or the `GetRequiredService` method as shown below:
````C#
public class MyService : ITransientDependency
{
- public ILogger Logger { get; set; }
+ private readonly ITaxCalculator _taxCalculator;
+
+ public MyService(IServiceProvider serviceProvider)
+ {
+ _taxCalculator = serviceProvider.GetRequiredService();
+ }
}
````
@@ -374,15 +383,15 @@ IEnumerable services = _serviceProvider.GetServices();
+ // TODO: Use the taxCalculator
+ }
+}
+
+````
+
+With such a usage, you don't need to deal with creating service scopes and disposing the resolved services (as explained in the *Releasing/Disposing Services* section above). Because all the services resolved from the `ICachedServiceProvider` will be released once the service scope of the `MyService` instance is disposed. Also, you don't need to care about memory leaks (because of creating too many `ITaxCalculator` instances if we call `DoSomethingAsync` too many times), because only one `ITaxCalculator` instance is created, and it is reused.
+
+Since `ICachedServiceProvider` and `ITransientCachedServiceProvider` extends the standard `IServiceProvider` interface, you can use all the extension method of the `IServiceProvider` interface on them. In addition, they provides some other methods to provide a default value or a factory method for the services that are not found (that means not registered to the dependency injection system). Notice that the default value (or the value returned from your factory method) is also cached and reused.
+
+Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unless you need to create the service cache per usage. `ITransientCachedServiceProvider` guarantees that the created service instances are not shared with any other service, even they are in the same service scope. The services resolved from `ICachedServiceProvider` are shared with other services in the same service scope (in the same HTTP Request, for example), so it can be thought as more optimized.
+
+> ABP Framework also provides the `IAbpLazyServiceProvider` service. It does exists for backward compatibility and works exactly same with the `ITransientCachedServiceProvider` service. So, use the `ITransientCachedServiceProvider` since the `IAbpLazyServiceProvider` might be removed in future ABP versions.
## Advanced Features
-### IServiceCollection.OnRegistred Event
+### IServiceCollection.OnRegistered Event
-You may want to perform an action for every service registered to the dependency injection. In the `PreConfigureServices` method of your module, register a callback using the `OnRegistred` method as shown below:
+You may want to perform an action for every service registered to the dependency injection. In the `PreConfigureServices` method of your module, register a callback using the `OnRegistered` method as shown below:
````csharp
public class AppModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
- context.Services.OnRegistred(ctx =>
+ context.Services.OnRegistered(ctx =>
{
var type = ctx.ImplementationType;
//...
@@ -421,7 +465,7 @@ public class AppModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
- context.Services.OnRegistred(ctx =>
+ context.Services.OnRegistered(ctx =>
{
if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true))
{
@@ -434,7 +478,7 @@ public class AppModule : AbpModule
This example simply checks if the service class has `MyLogAttribute` attribute and adds `MyLogInterceptor` to the interceptor list if so.
-> Notice that `OnRegistred` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](Dynamic-Proxying-Interceptors.md) of dynamic proxying / interceptors.
+> Notice that `OnRegistered` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](Dynamic-Proxying-Interceptors.md) of dynamic proxying / interceptors.
## 3rd-Party Providers
diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md
index 2dace9a7f7..e3294b3469 100644
--- a/docs/en/Deployment/Clustered-Environment.md
+++ b/docs/en/Deployment/Clustered-Environment.md
@@ -63,7 +63,7 @@ The [Database BLOB provider](../Blob-Storing-Database) is the easiest way since
> [ABP Commercial](https://commercial.abp.io/) startup solution templates come with the database BLOB provider as pre-installed, and stores BLOBs in the application's database.
-Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOG storage providers.
+Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOB storage providers.
## Configuring Background Jobs
diff --git a/docs/en/Deployment/Configuring-Production.md b/docs/en/Deployment/Configuring-Production.md
new file mode 100644
index 0000000000..db928c8590
--- /dev/null
+++ b/docs/en/Deployment/Configuring-Production.md
@@ -0,0 +1,104 @@
+# Configuring Your Application for Production Environments
+
+ABP Framework has a lot of options to configure and fine-tune its features. They are all explained in their own documents. Default values for these options are pretty well for most of the deployment environments. However, you may need to care about some options based on how you've structured your deployment environment. In this document, we will highlight these kind of options. So, it is highly recommended to read this document in order to not have unexpected behaviors in your system in production.
+
+## Distributed Cache Prefix
+
+ABP's [distributed cache infrastructure](../Caching.md) provides an option to set a key prefix for all of your data that is saved into your distributed cache provider. The default value of this option is not set (it is `null`). If you are using a distributed cache server that is shared by different applications, then you can set a prefix value to isolate an application's cache data from others.
+
+````csharp
+Configure(options =>
+{
+ options.KeyPrefix = "MyCrmApp";
+});
+````
+
+That's all. ABP, then will add this prefix to all of your cache keys in your application as along as you use ABP's `IDistributedCache` or `IDistributedCache` services. See the [Caching documentation](../Caching.md) if you are new to distributed caching.
+
+> **Warning**: If you use ASP.NET Core's standard `IDistributedCache` service, it's your responsibility to add the key prefix (you can get the value by injecting `IOptions`). ABP can not do it.
+
+> **Warning**: Even if you have never used distributed caching in your own codebase, ABP still uses it for some features. So, you should always configure this prefix if your caching server is shared among multiple systems.
+
+> **Warning**: If you are building a microservice system, then you will have multiple applications that share the same distributed cache server. In such systems, all applications (or services) should normally use the same cache prefix, because you want all the applications to use the same cache data to have consistency between them.
+
+> **Warning**: Some of ABP's startup templates are pre-configured to set a prefix value for the distributed cache. So, please check your application code if it is already configured.
+
+## Distributed Lock Prefix
+
+ABP's [distributed locking infrastructure](../Distributed-Locking.md) provides an option to set a prefix for all the keys you are using in the distributed lock server. The default value of this option is not set (it is `null`). If you are using a distributed lock server that is shared by different applications, then you can set a prefix value to isolate an application's lock from others.
+
+````csharp
+Configure(options =>
+{
+ options.KeyPrefix = "MyCrmApp";
+});
+````
+
+That's all. ABP, then will add this prefix to all of your keys in your application. See the [Distributed Locking documentation](../Distributed-Locking.md) if you are new to distributed locking.
+
+> **Warning**: Even if you have never used distributed locking in your own codebase, ABP still uses it for some features. So, you should always configure this prefix if your distributed lock server is shared among multiple systems.
+
+> **Warning**: If you are building a microservice system, then you will have multiple applications that share the same distributed locking server. In such systems, all applications (or services) should normally use the same lock prefix, because you want to globally lock your resources in your system.
+
+> **Warning**: Some of ABP's startup templates are pre-configured to set a prefix value for distributed locking. So, please check your application code if it is already configured.
+
+## Email Sender
+
+ABP's [Email Sending](../Emailing.md) system abstracts sending emails from your application and module code and allows you to configure the email provider and settings in a single place.
+
+Email service is configured to write email contents to the standard [application log](../Logging.md) in development environment. You should configure the email settings to be able to send emails to users in your production environment.
+
+Please see the [Email Sending](../Emailing.md) document to learn how to configure its settings to really send emails.
+
+> **Warning**: If you don't configure the email settings, you will get errors while trying to send emails. For example, the [Account module](../Modules/Account.md)'s *Password Reset* feature sends email to the users to reset their passwords if they forget it.
+
+## SMS Sender
+
+ABP's [SMS Sending abstraction](https://docs.abp.io/en/abp/latest/SMS-Sending) provides a unified interface to send SMS to users. However, its implementation is left to you. Because, typically a paid SMS service is used to send SMS, and ABP doesn't depend on a specific SMS provider.
+
+So, if you are using the `ISmsSender` service, you must implement it yourself, as shown in the following code block:
+
+````csharp
+public class MySmsSender : ISmsSender, ITransientDependency
+{
+ public async Task SendAsync(SmsMessage smsMessage)
+ {
+ // TODO: Send it using your provider...
+ }
+}
+````
+
+> [ABP Commercial](https://commercial.abp.io/) provides a [Twilio SMS Module](https://docs.abp.io/en/commercial/latest/modules/twilio-sms) as a pre-built integration with the popular [Twilio](https://www.twilio.com/) platform.
+
+## BLOB Provider
+
+If you use ABP's [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing) infrastructure, you should care about the BLOB provider in your production environment. For example, if you use the [File System](../Blob-Storing-File-System.md) provider and your application is running in a Docker container, you should configure a volume mapping for the BLOB storage path. Otherwise, your data will be lost when the container is restarted. Also, the File System is not a good provider for production if you have a [clustered deployment](Clustered-Environment.md) or a microservice system.
+
+Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOB storage providers.
+
+> **Warning**: Even if you don't directly use the BLOB Storage system, a module you are depending on may use it. For example, ABP Commercial's [File Management](https://docs.abp.io/en/commercial/latest/modules/file-management) module stores file contents, and the [Account](https://docs.abp.io/en/commercial/latest/modules/account) module stores user profile pictures in the BLOB Storage system. So, be careful with the BLOB Storing configuration in production. Note that ABP Commercial uses the [Database Provider](../Blob-Storing-Database.md) as a pre-configured BLOB storage provider, which works in production without any problem, but you may still want to use another provider.
+
+## String Encryption
+
+ABP's [`IStringEncryptionService` Service](../String-Encryption.md) simply encrypts and decrypts given strings based on a password phrase. You should configure the `AbpStringEncryptionOptions` options for the production with a strong password and keep it as a secret. You can also configure the other properties of those options class. See the following example:
+
+````csharp
+Configure(options =>
+{
+ options.DefaultPassPhrase = "gs5nTT042HAL4it1";
+});
+````
+
+Note that ABP CLI automatically sets the password to a random value on a new project creation. However, it is stored in the `appsettings.json` file and is generally added to your source control. It is suggested to use [User Secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) or [Environment Variables](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration) to set that value.
+
+## Logging
+
+ABP uses .NET's standard [Logging services](../Logging.md). So, it is compatible with any logging provider that works with .NET. ABP's startup solution templates come with [Serilog](https://serilog.net/) pre-installed and configured for you. It writes logs to file system and console with the initial configuration. File system is useful for development environment, but it is suggested you to use a different provider for your production environment, like Elasticsearch, database or any other provider that can properly work.
+
+## The Swagger UI
+
+ABP's startup solution templates come with [Swagger UI](https://swagger.io/) pre-installed. Swagger is a pretty standard and useful tool to discover and test your HTTP APIs on a built-in UI that is embedded into your application or service. It is typically used in development environment, but you may want to enable it on staging or production environments too.
+
+While you will always secure your HTTP APIs with other techniques (like the [Authorization](../Authorization.md) system), allowing malicious software and people to easily discover your HTTP API endpoint details can be considered as a security problem for some systems. So, be careful while taking the decision of enabling or disabling Swagger for the production environment.
+
+> You may also want to see the [ABP Swagger integration](../API/Swagger-Integration.md) document.
diff --git a/docs/en/Deployment/Distributed-Microservice.md b/docs/en/Deployment/Distributed-Microservice.md
new file mode 100644
index 0000000000..9af4a1c839
--- /dev/null
+++ b/docs/en/Deployment/Distributed-Microservice.md
@@ -0,0 +1,45 @@
+# Deploying Distributed / Microservice Solutions
+
+The ABP Framework is designed to consider distributed and microservice systems, where you have multiple applications and/or services communicating internally. All of its features are compatible with distributed scenarios. This document highlights some points you should care about when you deploy your distributed or microservice solution.
+
+## Application Name & Instance Id
+
+ABP provides the `IApplicationInfoAccessor` service that provides the following properties:
+
+* `ApplicationName`: A human-readable name for an application. It is a unique value for an application.
+* `InstanceId`: A random (GUID) value generated by the ABP Framework each time you start the application.
+
+These values are used by the ABP Framework in several places to distinguish the application and the application instance (process) in the system. For example, the [audit logging](../Audit-Logging.md) system saves the `ApplicationName` in each audit log record written by the related application, so you can understand which application has created the audit log entry. So, if your system consists of multiple applications saving audit logs to a single point, you should be sure that each application has a different `ApplicationName`.
+
+The `ApplicationName` property's value is set automatically from the **entry assembly's name** (generally, the project name in a .NET solution) by default, which is proper for most cases, since each application typically has a unique entry assembly name.
+
+There are two ways to set the application name to a different value. In this first approach, you can set the `ApplicationName` property in your application's [configuration](../Configuration.md). The easiest way is to add an `ApplicationName` field to your `appsettings.json` file:
+
+````json
+{
+ "ApplicationName": "Services.Ordering"
+}
+````
+
+Alternatively, you can set `AbpApplicationCreationOptions.ApplicationName` while creating the ABP application. You can find the `AddApplication` or `AddApplicationAsync` call in your solution (typically in the `Program.cs` file), and set the `ApplicationName` option as shown below:
+
+````csharp
+await builder.AddApplicationAsync(options =>
+{
+ options.ApplicationName = "Services.Ordering";
+});
+````
+
+## Using a Distributed Event Bus
+
+ABP's [Distributed Event Bus](../Distributed-Event-Bus.md) system provides a standard interface to communicate with other applications and services. While the name is "distributed", the default implementation is in-process. That means, your applications / services can not communicate with each other unless you explicitly configure a distributed event bus provider.
+
+If you are building a distributed system, then the applications should communicate through an external distributed messaging server. Please follow the [Distributed Event Bus](../Distributed-Event-Bus.md) document to learn how to install and configure your distributed event bus provider.
+
+> **Warning**: Even if you don't use the distributed event bus directly in your application code, the ABP Framework and some of the modules you are using may use it. So, if you are building a distributed system, always configure a distributed event bus provider.
+
+> **Info**: [Clustered deployment](Clustered-Environment.md) of a single application is not considered as a distributed system. So, if you only have a single application with multiple instances serving behind a load balancer, a real distributed messaging server may not be needed.
+
+## See Also
+
+* [Deploying to a Clustered Environment](Clustered-Environment.md)
diff --git a/docs/en/Deployment/Index.md b/docs/en/Deployment/Index.md
index 6503f8b664..849d3bd4f3 100644
--- a/docs/en/Deployment/Index.md
+++ b/docs/en/Deployment/Index.md
@@ -6,4 +6,7 @@ However, there are some topics that you should care about when you are deploying
## Guides
-* [Deploying to a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently.
+* [Configuring for Production](Configuring-Production.md): Notes for some essential configurations for production environments.
+* [Optimization for Production](Optimizing-Production.md): Tips and suggestions for optimizing your application on production environments.
+* [Deploying to a Clustered Environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently.
+* [Deploying Distributed / Microservice Solutions](Distributed-Microservice.md): Deployment notes for solutions consisting of multiple applications and/or services.
diff --git a/docs/en/Deployment/Optimizing-Production.md b/docs/en/Deployment/Optimizing-Production.md
new file mode 100644
index 0000000000..7e896f9b1c
--- /dev/null
+++ b/docs/en/Deployment/Optimizing-Production.md
@@ -0,0 +1,22 @@
+# Optimizing Your Application for Production Environments
+
+ABP Framework and the startup solution templates are configured well to get the maximum performance on production environments. However, there are still some points you need to pay attention to in order to optimize your system in production. In this document, we will mention some of these topics.
+
+## Caching Static Contents
+
+The following items are contents that can be cached in the client side (typically in the Browser) or in a CDN server:
+
+* **Static images** can always be cached. Here, you should be careful that if you change an image, use a different file name, or use a versioning query-string parameter, so the browser (or CDN) understands it's been changed.
+* **CSS and JavaScript files**. ABP's [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) system always uses a query-string versioning parameter and a hash value in the files names of the CSS & JavaScript files for the [MVC (Razor Pages)](../UI/AspNetCore/Overall.md) UI. So, you can safely cache these files in the client side or in a CDN server.
+* **Application bundle files** of an [Angular UI](../UI/Angular/Quick-Start.md) application.
+* **[Application Localization Endpoint](../API/Application-Localization.md)** can be cached per culture (it already has a `cultureName` query string parameter) if you don't use dynamic localization on the server-side. ABP Commercial's [Language Management](https://commercial.abp.io/modules/Volo.LanguageManagement) module provides dynamic localization. If you're using it, you can't cache that endpoint forever. However, you can still cache it for a while. Applying dynamic localization text changes to the application can delay for a few minutes, even for a few hours in a real life scenario.
+
+There may be more ways based on your solution structure and deployment environment, but these are the essential points you should consider to client-side cache in a production environment.
+
+## Bundling & Minification for MVC (Razor Pages) UI
+
+ABP's [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) system automatically bundles, minifies and versions your CSS and JavaScript files in production environment. Normally, you don't need to do anything, if you haven't disabled it yourself in your application code. It is important to follow the [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) document and truly use the system to get the maximum optimization.
+
+## Background Jobs
+
+ABP's [Background Jobs](../Background-Jobs.md) system provides an abstraction with a basic implementation to enqueue jobs and execute them in a background thread. ABP's Default Background Job Manager may not be enough if you are adding too many jobs to the queue and want them to be executed in parallel by multiple servers with a high performance. If you need these, you should consider to configure a dedicated background job software, like [Hangfire](https://www.hangfire.io/). ABP has a pre-built [Hangfire integration](../Background-Jobs-Hangfire.md), so you can switch to Hangfire without changing your application code.
diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md
index 21b1b07256..f1b8464d59 100644
--- a/docs/en/Distributed-Event-Bus.md
+++ b/docs/en/Distributed-Event-Bus.md
@@ -296,6 +296,74 @@ This example;
> Distributed event system use the [object to object mapping](Object-To-Object-Mapping.md) system to map `Product` objects to `ProductEto` objects. So, you need to configure the object mapping (`Product` -> `ProductEto`) too. You can check the [object to object mapping document](Object-To-Object-Mapping.md) to learn how to do it.
+## Entity Synchronizer
+
+In a distributed (or microservice) system, it is typical to subscribe to change events for an [entity](Entities.md) type of another service, so you can get notifications when the subscribed entity changes. In that case, you can use ABP's Pre-Defined Events as explained in the previous section.
+
+If your purpose is to store your local copies of a remote entity, you typically subscribe to create, update and delete events of the remote entity and update your local database in your event handler. ABP provides a pre-built `EntitySynchronizer` base class to make that operation easier for you.
+
+Assume that there is a `Product` entity (probably an aggregate root entity) in a Catalog microservice, and you want to keep copies of the products in your Ordering microservice, with a local `OrderProduct` entity. In practice, properties of the `OrderProduct` class will be a subset of the `Product` properties, because not all the product data is needed in the Ordering microservice (however, you can make a full copy if you need). Also, the `OrderProduct` entity may have additional properties that are populated and used in the Ordering microservice.
+
+The first step to establish the synchronization is to define an ETO (Event Transfer Object) class in the Catalog microservice that is used to transfer the event data. Assuming the `Product` entity has a `Guid` key, your ETO can be as shown below:
+
+````csharp
+[EventName("product")]
+public class ProductEto : EntityEto
+{
+ // Your Product properties here...
+}
+````
+
+`ProductEto` can be put in a shared project (DLL) that is referenced by the Catalog and the Ordering microservices. Alternatively, you can put a copy of the `ProductEto` class in the Ordering microservice if you don't want to introduce a common project dependency between the services. In this case, the `EventName` attribute becomes critical to map the `ProductEto` classes across two services (you should use the same event name).
+
+Once you define an ETO class, you should configure the ABP Framework to publish auto (create, update and delete) events for the `Product` entity, as explained in the previous section:
+
+````csharp
+Configure(options =>
+{
+ options.AutoEventSelectors.Add();
+ options.EtoMappings.Add();
+});
+````
+
+Finally, you should create a class in the Ordering microservice, that is derived from the `EntitySynchronizer` class:
+
+````csharp
+public class ProductSynchronizer : EntitySynchronizer
+{
+ public ProductSynchronizer(
+ IObjectMapper objectMapper,
+ IRepository repository
+ ) : base(objectMapper, repository)
+ {
+ }
+}
+````
+
+The main point of this class is it subscribes to the create, update and delete events of the source entity and updates the local entity in the database. It uses the [Object Mapper](Object-To-Object-Mapping.md) system to create or update the `OrderProduct` objects from the `ProductEto` objects. So, you should also configure the object mapper to make it properly work. Otherwise, you should manually perform the object mapping by overriding the `MapToEntityAsync(TSourceEntityEto)` and `MapToEntityAsync(TSourceEntityEto,TEntity)` methods in your `ProductSynchronizer` class.
+
+If your entity has a composite primary key (see the [Entities document](Entities.md)), then you should inherit from the `EntitySynchronizer` class (just don't use the `Guid` generic argument in the previous example) and implement `FindLocalEntityAsync` to find the entity in your local database using the `Repository`.
+
+`EntitySynchronizer` is compatible with the *Entity Versioning* system (see the [Entities document](Entities.md)). So, it works as expected even if the events are received as disordered. If the entity's version in your local database is newer than the entity in the received event, then the event is ignored. You should implement the `IHasEntityVersion` interface for the entity and ETO classes (for this example, you should implement for the `Product`, `ProductEto` and `OrderProduct` classes).
+
+If you want to ignore some type of change events, you can set `IgnoreEntityCreatedEvent`, `IgnoreEntityUpdatedEvent` and `IgnoreEntityDeletedEvent` in the constructor of your class. Example:
+
+````csharp
+public class ProductSynchronizer
+ : EntitySynchronizer
+{
+ public ProductSynchronizer(
+ IObjectMapper objectMapper,
+ IRepository repository
+ ) : base(objectMapper, repository)
+ {
+ IgnoreEntityDeletedEvent = true;
+ }
+}
+````
+
+> Notice that the `EntitySynchronizer` can only create/update the entities after you use it. If you have an existing system with existing data, you should manually copy the data for one time, because the `EntitySynchronizer` starts to work.
+
## Transaction and Exception Handling
Distributed event bus works in-process (since default implementation is `LocalDistributedEventBus`) unless you configure an actual provider (e.g. [Kafka](Distributed-Event-Bus-Kafka-Integration.md) or [RabbitMQ](Distributed-Event-Bus-RabbitMQ-Integration.md)). In-process event bus always executes event handlers in the same [unit of work](Unit-Of-Work.md) scope that you publishes the events in. That means, if an event handler throws an exception, then the related unit of work (the database transaction) is rolled back. In this way, your application logic and event handling logic becomes transactional (atomic) and consistent. If you want to ignore errors in an event handler, you must use a `try-catch` block in your handler and shouldn't re-throw the exception.
@@ -434,6 +502,29 @@ Here, the following properties are available on the `config` object:
* `HandlerSelector`: A predicate to filter the event handled types (classes implementing the `IDistributedEventHandler` interface) to be used for this configuration. This is especially useful if you want to ignore some event handler types from inbox processing, or want to define named inbox configurations and group event handlers within these configurations. See the *Named Configurations* section.
* `ImplementationType`: Type of the class that implements the database operations for the inbox. This is normally set when you call `UseDbContext` as shown before. See *Implementing a Custom Outbox/Inbox Database Provider* section for advanced usages.
+#### AbpEventBusBoxesOptions
+
+`AbpEventBusBoxesOptions` can be used to fine-tune how inbox and outbox systems work. For most of the systems, using the defaults would be more than enough, but you can configure it to optimize your system when it is needed.
+
+Just like all the [options classes](Options.md), `AbpEventBusBoxesOptions` can be configured in the `ConfigureServices` method of your [module class](Module-Development-Basics.md) as shown in the following code block:
+
+````csharp
+Configure(options =>
+{
+ // TODO: configure the options
+});
+````
+
+`AbpEventBusBoxesOptions` has the following properties to be configured:
+
+* `BatchPublishOutboxEvents`: Can be used to enable or disable batch publishing events to the message broker. Batch publishing works if it is supported by the distributed event bus provider. If not supported, events are sent one by one as the fallback logic. Keep it as enabled since it has a great performance gain wherever possible. Default value is `true` (enabled).
+* `PeriodTimeSpan`: The period of the inbox and outbox message processors to check if there is a new event in the database. Default value is 2 seconds (`TimeSpan.FromSeconds(2)`).
+* `CleanOldEventTimeIntervalSpan`: The event inbox system periodically checks and deletes the old processed events from the inbox in the database. You can set this value to determine the check period. Default value is 6 hours (`TimeSpan.FromHours(6)`).
+* `WaitTimeToDeleteProcessedInboxEvents`: Inbox events are not deleted from the database for a while even if they are successfully processed. This is for a system to prevent multiple process of the same event (if the event broker sends it twice). This configuration value determines the time to keep the processed events. Default value is 2 hours (`TimeSpan.FromHours(2)`).
+* `InboxWaitingEventMaxCount`: The maximum number of events to query at once from the inbox in the database. Default value is 1000.
+* `OutboxWaitingEventMaxCount`: The maximum number of events to query at once from the outbox in the database. Default value is 1000.
+* `DistributedLockWaitDuration`: ABP uses [distributed locking](Distributed-Locking.md) to prevent concurrent access to the inbox and outbox messages in the database, when running multiple instance of the same application. If an instance of the application can not obtain the lock, it tries after a duration. This is the configuration of that duration. Default value is 15 seconds (`TimeSpan.FromSeconds(15)`).
+
### Skipping Outbox
`IDistributedEventBus.PublishAsync` method provides an optional parameter, `useOutbox`, which is set to `true` by default. If you bypass outbox and immediately publish an event, you can set it to `false` for a specific event publishing operation.
diff --git a/docs/en/Entities.md b/docs/en/Entities.md
index ef8f52a04f..124e0adab1 100644
--- a/docs/en/Entities.md
+++ b/docs/en/Entities.md
@@ -316,6 +316,29 @@ All these base classes also have non-generic versions to take `AuditedEntity` an
All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser` and `FullAuditedAggregateRootWithUser`. This makes possible to add a navigation property to your user entity. However, it is not a good practice to add navigation properties between aggregate roots, so this usage is not suggested (unless you are using an ORM, like EF Core, that well supports this scenario and you really need it - otherwise remember that this approach doesn't work for NoSQL databases like MongoDB where you must truly implement the aggregate pattern). Also, if you add navigation properties to the AppUser class that comes with the startup template, consider to handle (ignore/map) it on the migration dbcontext (see [the EF Core migration document](Entity-Framework-Core-Migrations.md)).
+## Caching Entities
+
+ABP Framework provides a [Distributed Entity Cache System](Entity-Cache.md) for caching entities. It is useful if you want to use caching for quicker access to the entity rather than repeatedly querying it from the database.
+
+It's designed as read-only and automatically invalidates a cached entity if the entity is updated or deleted.
+
+> See the [Entity Cache](Entity-Cache.md) documentation for more information.
+
+## Versioning Entities
+
+ABP defines the `IHasEntityVersion` interface for automatic versioning of your entities. It only provides a single `EntityVersion` property, as shown in the following code block:
+
+````csharp
+public interface IHasEntityVersion
+{
+ int EntityVersion { get; }
+}
+````
+
+If you implement the `IHasEntityVersion` interface, ABP automatically increases the `EntityVersion` value whenever you update your entity. The initial `EntityVersion` value will be `0`, when you first create an entity and save to the database.
+
+> ABP can not increase the version if you directly execute SQL `UPDATE` commands in the database. It is your responsibility to increase the `EntityVersion` value in that case. Also, if you are using the aggregate pattern and change sub-collections of an aggregate root, it is your responsibility if you want to increase the version of the aggregate root object.
+
## Extra Properties
ABP defines the `IHasExtraProperties` interface that can be implemented by an entity to be able to dynamically set and get properties for the entity. `AggregateRoot` base class already implements the `IHasExtraProperties` interface. If you've derived from this class (or one of the related audit class defined above), you can directly use the API.
diff --git a/docs/en/Entity-Cache.md b/docs/en/Entity-Cache.md
new file mode 100644
index 0000000000..834a0d5313
--- /dev/null
+++ b/docs/en/Entity-Cache.md
@@ -0,0 +1,131 @@
+# Entity Cache
+
+ABP Framework provides an entity caching system that works on top of the [distributed caching](Caching.md) system. It does the following operations on behalf of you:
+
+* Gets the entity from the database (by using the [repositories](Repositories.md)) in its first call and then gets it from the cache in subsequent calls.
+* Automatically invalidates the cached entity if the entity is updated or deleted. Thus, it will be retrieved from the database in the next call and will be re-cached.
+
+## Caching Entity Objects
+
+`IEntityCache` is a simple service provided by the ABP Framework for caching entities. Assume that you have a `Product` entity as shown below:
+
+```csharp
+public class Product : AggregateRoot
+{
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public float Price { get; set; }
+ public int StockCount { get; set; }
+}
+```
+
+If you want to cache this entity, you should first configure the [dependency injection](Dependency-Injection.md) system to register the `IEntityCache` service in the `ConfigureServices` method of your [module class](Module-Development-Basics.md):
+
+```csharp
+context.Services.AddEntityCache();
+```
+
+Now you can inject the `IEntityCache` service wherever you need:
+
+```csharp
+public class ProductAppService : ApplicationService, IProductAppService
+{
+ private readonly IEntityCache _productCache;
+
+ public ProductAppService(IEntityCache productCache)
+ {
+ _productCache = productCache;
+ }
+
+ public async Task GetAsync(Guid id)
+ {
+ var product = await _productCache.GetAsync(id);
+ return ObjectMapper.Map(product);
+ }
+}
+```
+
+> Note that we've used the `ObjectMapper` service to map from `Product` to `ProductDto`. You should configure that [object mapping](Object-To-Object-Mapping.md) to make that example service properly works.
+
+That's all. The cache name (in the distributed cache server) will be full name (with namespace) of the `Product` class. You can use the `[CacheName]` attribute to change it. Please refer to the [caching document](Caching.md) for details.
+
+## Using a Cache Item Class
+
+In the previous section, we've directly cached the `Product` entity. In that case, the `Product` class must be serializable to JSON (and deserializable from JSON). Sometimes that might not be possible or you may want to use another class to store the cache data. For example, we may want to use the `ProductDto` class instead of the `Product` class for the cached object if the `Product` entity.
+
+Assume that we've created a `ProductDto` class as shown below:
+
+```csharp
+public class ProductDto : EntityDto
+{
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public float Price { get; set; }
+ public int StockCount { get; set; }
+}
+```
+
+Now, we can register the entity cache services to [dependency injection](Dependency-Injection.md) in the `ConfigureServices` method of your [module class](Module-Development-Basics.md) with three generic parameters, as shown below:
+
+```csharp
+context.Services.AddEntityCache();
+```
+
+Since the entity cache system will perform the [object mapping](Object-To-Object-Mapping.md) (from `Product` to `ProductDto`), we should configure the object map. Here, an example configuration with [AutoMapper](https://automapper.org/):
+
+```csharp
+public class MyMapperProfile : Profile
+{
+ public MyMapperProfile()
+ {
+ CreateMap();
+ }
+}
+```
+
+Now, you can inject the `IEntityCache` service wherever you want:
+
+```csharp
+public class ProductAppService : ApplicationService, IProductAppService
+{
+ private readonly IEntityCache _productCache;
+
+ public ProductAppService(IEntityCache productCache)
+ {
+ _productCache = productCache;
+ }
+
+ public async Task GetAsync(Guid id)
+ {
+ return await _productCache.GetAsync(id);
+ }
+}
+```
+
+Notice that the `_productCache.GetAsync` method already returns a `ProductDto` object, so we could directly return it from out application service.
+
+## Configuration
+
+All of the `context.Services.AddEntityCache()` methods get an optional `DistributedCacheEntryOptions` parameter where you can easily configure the caching options:
+
+```csharp
+context.Services.AddEntityCache(
+ new DistributedCacheEntryOptions
+ {
+ SlidingExpiration = TimeSpan.FromMinutes(30)
+ }
+);
+```
+
+> The default cache duration is **2 minutes** with the `AbsoluteExpirationRelativeToNow` configuration.
+
+## Additional Notes
+
+* Entity classes should be serializable/deserializable to/from JSON to be cached (because it's serialized to JSON when saving in the [Distributed Cache](Caching.md)). If your entity class is not serializable, you can consider using a cache-item/DTO class instead, as explained before.
+* Entity Caching System is designed as **read-only**. You should use the standard [repository](Repositories.md) methods to manipulate the entity if you need. If you need to manipulate (update) the entity, do not get it from the entity cache. Instead, read it from the repository, change it and update using the repository.
+
+## See Also
+
+* [Distributed caching](Caching.md)
+* [Entities](Entities.md)
+* [Repositories](Repositories.md)
diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md
index 568623a15d..fc17ddcd49 100644
--- a/docs/en/Entity-Framework-Core.md
+++ b/docs/en/Entity-Framework-Core.md
@@ -834,7 +834,7 @@ One advantage of using an interface for a DbContext is then it will be replaceab
Once you properly define and use an interface for DbContext, then any other implementation can use the following ways to replace it:
-**ReplaceDbContextAttribute**
+#### ReplaceDbContext Attribute
```csharp
[ReplaceDbContext(typeof(IBookStoreDbContext))]
@@ -844,7 +844,7 @@ public class OtherDbContext : AbpDbContext, IBookStoreDbContext
}
```
-**ReplaceDbContext option**
+#### ReplaceDbContext Option
````csharp
context.Services.AddAbpDbContext(options =>
@@ -856,6 +856,22 @@ context.Services.AddAbpDbContext(options =>
In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime.
+#### Replacing with Multi-Tenancy
+
+It is also possible to replace a DbContext based on the [multi-tenancy](Multi-Tenancy.md) side. `ReplaceDbContext` attribute and `ReplaceDbContext` method can get a `MultiTenancySides` option with a default value of `MultiTenancySides.Both`.
+
+**Example:** Replace DbContext only for tenants, using the `ReplaceDbContext` attribute
+
+````csharp
+[ReplaceDbContext(typeof(IBookStoreDbContext), MultiTenancySides.Tenant)]
+````
+
+**Example:** Replace DbContext only for the host side, using the `ReplaceDbContext` method
+
+````csharp
+options.ReplaceDbContext(MultiTenancySides.Host);
+````
+
### Split Queries
ABP enables [split queries](https://docs.microsoft.com/en-us/ef/core/querying/single-split-queries) globally by default for better performance. You can change it as needed.
diff --git a/docs/en/Getting-Started-AspNetCore-Application.md b/docs/en/Getting-Started-AspNetCore-Application.md
index c7f329878c..117b0b499e 100644
--- a/docs/en/Getting-Started-AspNetCore-Application.md
+++ b/docs/en/Getting-Started-AspNetCore-Application.md
@@ -1,4 +1,4 @@
-# Getting Started With an ABP and AspNet Core MVC Web Application
+# Getting Started with an Empty ASP.NET Core MVC / Razor Pages Application
This tutorial explains how to start ABP from scratch with minimal dependencies. You generally want to start with the **[startup template](Getting-Started-AspNetCore-MVC-Template.md)**.
diff --git a/docs/en/Getting-Started-React-Native.md b/docs/en/Getting-Started-React-Native.md
index fe4cf478de..be0505b9f1 100644
--- a/docs/en/Getting-Started-React-Native.md
+++ b/docs/en/Getting-Started-React-Native.md
@@ -95,6 +95,20 @@ A React Native application running on an Android emulator or a physical phone **
Run the backend application as described in the [getting started document](Getting-Started.md).
+> You should turn off the "Https Restriction" if you're using OpenIddict as a central identity management solution. Because the IOS Simulator doesn't support self-signed certificates and OpenIddict is set to only work with HTTPS by default.
+## How to disable the Https-only settings of OpenIddict
+
+ Go to MyProjectNameHttpApiHostModule.cs under the host project. Add put these codes under the `PreConfigureServices` function.
+
+```csharp
+#if DEBUG
+ PreConfigure(options => {
+ options.UseAspNetCore()
+ .DisableTransportSecurityRequirement();
+ });
+#endif
+```
+
## How to Configure & Run the React Native Application
diff --git a/docs/en/Getting-Started-Running-Solution.md b/docs/en/Getting-Started-Running-Solution.md
index 44fbd3eb03..36cd5ffe0a 100644
--- a/docs/en/Getting-Started-Running-Solution.md
+++ b/docs/en/Getting-Started-Running-Solution.md
@@ -113,11 +113,11 @@ This is the HTTP API that is used by the web application.
3. Lastly, ensure that the {{if UI=="MVC"}}`.Web`{{else}}`.Blazor`{{end}} project is the startup project and run the application which will open a **welcome** page in your browser
-
+
Click to the **login** button which will redirect you to the *authentication server* to login to the application:
-
+
{{ else # Tiered != "Yes" }}
@@ -125,7 +125,7 @@ Ensure that the {{if UI=="MVC"}}`.Web`{{else}}`.Blazor`{{end}} project is the st
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
-
+
{{ end # Tiered }}
@@ -171,7 +171,7 @@ Ensure that the `.Blazor` project is the startup project and run the application
Once the application starts, click to the **Login** link on to header, which redirects you to the authentication server to enter a username and password:
-
+
{{ else if UI == "NG" }}
@@ -191,7 +191,7 @@ yarn start
It may take a longer time for the first build. Once it finishes, it opens the Angular UI in your default browser with the [localhost:4200](http://localhost:4200/) address.
-
+
{{ end }}
diff --git a/docs/en/Getting-Started-Setup-Environment.md b/docs/en/Getting-Started-Setup-Environment.md
index 1a47c67d34..f6df09badf 100644
--- a/docs/en/Getting-Started-Setup-Environment.md
+++ b/docs/en/Getting-Started-Setup-Environment.md
@@ -19,7 +19,7 @@ First things first! Let's setup your development environment before creating the
The following tools should be installed on your development machine:
-* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 6.0+](https://dotnet.microsoft.com/download/dotnet) development.
+* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 7.0+](https://dotnet.microsoft.com/download/dotnet) development.
{{ if UI != "Blazor" }}
* [Node v16 or v18](https://nodejs.org/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) [1](#f-yarn) or npm v6+ (already installed with Node)
diff --git a/docs/en/Getting-Started.md b/docs/en/Getting-Started.md
index 0304eb2320..6352f43bc7 100644
--- a/docs/en/Getting-Started.md
+++ b/docs/en/Getting-Started.md
@@ -11,8 +11,6 @@
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
-## Contents
-
This tutorial explains how to **create and run** a new web application using the ABP Framework. Follow the steps below;
1. [Setup your development environment](Getting-Started-Setup-Environment.md)
diff --git a/docs/en/Integration-Services.md b/docs/en/Integration-Services.md
new file mode 100644
index 0000000000..966b383ef7
--- /dev/null
+++ b/docs/en/Integration-Services.md
@@ -0,0 +1,80 @@
+# Integration Services
+
+The *Integration Service* concept was created to distinguish the [application services](Application-Services.md) that are built for inter-module (or inter-microservice) communication from the application services that are intended to be consumed from a UI or a client application.
+
+The following figure shows a few microservices behind an API Gateway that is consumed by a UI application and 3rd-party client applications:
+
+
+
+HTTP requests coming from out of the API Gateway can be called as *external request*, while the HTTP requests performed between microservices can be considered as *internal requests*. The application services that are designed to respond to these external requests are called as *integration services*, because their purpose is to integrate microservices in the system, rather than respond to user requests.
+
+## Marking an Application Service as Integration Service
+
+Assume that you have an application service named `ProductAppService`, and you want to use that application service as an integration service. In that case, you can use the `[IntegrationService]` attribute on top of the application service class as shown below:
+
+```csharp
+[IntegrationService]
+public class ProductAppService : ApplicationService, IProductAppService
+{
+ // ...
+}
+```
+
+If your application service has an interface, like `IProductService` in this example, you can use it on the service interface:
+
+```csharp
+[IntegrationService]
+public interface IProductAppService : IApplicationService
+{
+ // ...
+}
+```
+
+> If you've used the `[IntegrationService]` on top of your service interface, it is *not needed* to use on the service class too.
+
+That's all. From now, ABP will handle your application service as integration service and implement the followings by convention:
+
+* If you are using the [Auto API Controllers](API/Auto-API-Controllers.md) feature in your application, the URL prefix will be `/integration-api` instead of `/api` for your integration services. Thus, you can distinguish internal and external service communications and take additional actions, such as preventing REST API calls for integration services out of API Gateway.
+* Audit logging is disabled by default for the integration services. See the next section if you want to enable it.
+
+## Configuration
+
+### Enabling/Disabling the Audit Logging
+
+Audit Logging is disabled by default for integration services but it can be enabled by configuring the `AbpAuditingOptions` [options class](Options.md) in the `ConfigureServices` method of your [module class](Module-Development-Basics.md):
+
+```csharp
+Configure(options =>
+{
+ options.IsEnabledForIntegrationService = true;
+});
+```
+
+> Please refer to the [audit logging document](Audit-Logging.md) for other options and details.
+
+### Filtering Auto API Controllers
+
+You can filter integration services (or non-integration services) while creating [Auto API Controllers](API/Auto-API-Controllers.md), using the `ApplicationServiceTypes` option of the `ConventionalControllerSetting` by configuring the `AbpAspNetCoreMvcOptions` as shown below:
+
+```csharp
+Configure(options =>
+{
+ options.ConventionalControllers.Create(
+ typeof(MyApplicationModule).Assembly,
+ conventionalControllerSetting =>
+ {
+ conventionalControllerSetting.ApplicationServiceTypes =
+ ApplicationServiceTypes.IntegrationServices;
+ });
+});
+```
+
+Tip: You can call the `options.ConventionalControllers.Create` multiple times to configure regular application services and integration services with different options.
+
+> Please refer to the [Auto API Controllers document](API/Auto-API-Controllers.md) for more information about the Auto API Controller system.
+
+## See Also
+
+* [Application Services](Application-Services.md)
+* [Auto API Controllers](API/Auto-API-Controllers.md)
+* [Audit Logging](Audit-Logging.md)
\ No newline at end of file
diff --git a/docs/en/JSON.md b/docs/en/JSON.md
index 27693bafda..d4fc550982 100644
--- a/docs/en/JSON.md
+++ b/docs/en/JSON.md
@@ -66,3 +66,9 @@ Add [Volo.Abp.Json.Newtonsoft](https://www.nuget.org/packages/Volo.Abp.Json.Newt
#### AbpNewtonsoftJsonSerializerOptions
- **JsonSerializerSettings(`Newtonsoft.Json.JsonSerializerSettings`)**: Global options for Newtonsoft library operations. See [here](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonSerializerSettings.htm) for reference.
+
+## Configuring JSON options in ASP.NET Core
+
+You can change the JSON behavior in ASP.NET Core by configuring [JsonOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.jsonoptions) or
+[MvcNewtonsoftJsonOptions](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.mvcnewtonsoftjsonoptions)(if you use `Newtonsoft.Json`)
+
diff --git a/docs/en/KB/Windows-Path-Too-Long-Fix.md b/docs/en/KB/Windows-Path-Too-Long-Fix.md
new file mode 100644
index 0000000000..e463288239
--- /dev/null
+++ b/docs/en/KB/Windows-Path-Too-Long-Fix.md
@@ -0,0 +1,10 @@
+# How to Fix "Filename too long" Error on Windows
+
+If you encounter the "filename too long" or "unzip" error on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
+
+If you face long path errors related to Git, try the following command to enable long paths in Windows.
+```
+git config --system core.longpaths true
+```
+
+See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
\ No newline at end of file
diff --git a/docs/en/Migration-Guides/Abp-7_0.md b/docs/en/Migration-Guides/Abp-7_0.md
index 3b6b1c5e6a..65a1614603 100644
--- a/docs/en/Migration-Guides/Abp-7_0.md
+++ b/docs/en/Migration-Guides/Abp-7_0.md
@@ -101,3 +101,113 @@ See https://github.com/abpframework/abp/pull/13644 for more info.
We've already done this for our themes.
See https://github.com/abpframework/abp/pull/13845 for more info.
+
+## Replaced `BlogPostPublicDto` with `BlogPostCommonDto`
+
+- In the CMS Kit Module, `BlogPostPublicDto` has been moved to `Volo.CmsKit.Common.Application.Contracts` from `Volo.CmsKit.Public.Application.Contracts` and renamed to `BlogPostCommonDto`.
+
+- See the [PR#13499](https://github.com/abpframework/abp/pull/13499) for more information.
+
+> You can ignore this if you don't use CMS Kit Module.
+
+## Data migration environment
+
+Please call `AddDataMigrationEnvironment` method in the migration project.
+
+```cs
+using (var application = await AbpApplicationFactory.CreateAsync(options =>
+{
+ //...
+ options.AddDataMigrationEnvironment();
+}))
+{
+ //...
+}
+```
+
+```cs
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.AddDataMigrationEnvironment();
+// Call AddDataMigrationEnvironment before AddApplicationAsync
+await builder.AddApplicationAsync();
+//...
+```
+
+See https://github.com/abpframework/abp/pull/13985 for more info.
+
+## Devart.Data.Oracle.EFCore
+
+The `Devart.Data.Oracle.EFCore` package do not yet support EF Core 7.0, If you use `AbpEntityFrameworkCoreOracleDevartModule(Volo.Abp.EntityFrameworkCore.Oracle.Devart)` may not work as expected, We will release new packages as soon as they are updated.
+
+See https://github.com/abpframework/abp/issues/14412 for more info.
+# Changes on Angular Apps
+## Added a new package `@abp/ng.oauth`
+OAuth Functionality moved to a seperate package named `@abp/ng.oauth`, so ABP users should add the `@abp/ng.oauth` packages on app.module.ts.
+Add the new npm package to your app.
+```
+yarn add @abp/ng.oauth
+// or npm i ---save @abp/ng.oauth
+```
+
+```typescript
+// app.module.ts
+import { AbpOAuthModule } from "@abp/ng.oauth";
+// ...
+@NgModule({
+ // ...
+ imports: [
+ AbpOAuthModule.forRoot(), // <-- Add This
+ // ...
+ ],
+ // ...
+})
+export class AppModule {}
+
+```
+## Lepton X Google-Font
+If you are using LeptonX that has google fonts, the fonts were built-in the Lepton file. It's been moved to a seperate file. So the ABP user should add font-bundle in angular.json. ( under the 'yourProjectName' > 'architect' > 'build' > 'options' >'styles' )
+
+// for LeptonX Lite
+```json
+ {
+ input: 'node_modules/@volo/ngx-lepton-x.lite/assets/css/font-bundle.rtl.css',
+ inject: false,
+ bundleName: 'font-bundle.rtl',
+ },
+ {
+ input: 'node_modules/@volo/ngx-lepton-x.lite/assets/css/font-bundle.css',
+ inject: false,
+ bundleName: 'font-bundle',
+ },
+```
+
+// for LeptonX
+```json
+ {
+ input: 'node_modules/@volosoft/ngx-lepton-x/assets/css/font-bundle.css',
+ inject: false,
+ bundleName: 'font-bundle',
+ },
+ {
+ input: 'node_modules/@volosoft/ngx-lepton-x/assets/css/font-bundle.rtl.css',
+ inject: false,
+ bundleName: 'font-bundle.rtl',
+ },
+```
+
+## Updated Side Menu Layout
+
+In side menu layout, eThemeLeptonXComponents.Navbar has been changed to eThemeLeptonXComponents.Toolbar, and
+eThemeLeptonXComponents.Sidebar to eThemeLeptonXComponents.Navbar.
+
+And also added new replaceable component like Logo Component, Language Component etc.
+
+If you are using replaceable component system you can check [documentation](https://docs.abp.io/en/commercial/latest/themes/lepton-x/angular#customization).
+
+
+## ng-zorro-antd-tree.css
+
+ng-zorro-antd-tree.css file should be in angular.json if the user uses AbpTree component or Abp-commercial. The ABP User should add this style definition on angular.json. ( under the 'yourProjectName' > 'architect' > 'build' > 'options' >'styles' )
+
+{ "input": "node_modules/ng-zorro-antd/tree/style/index.min.css", "inject": false, "bundleName": "ng-zorro-antd-tree" },
+
diff --git a/docs/en/Migration-Guides/Abp-7_1.md b/docs/en/Migration-Guides/Abp-7_1.md
new file mode 100644
index 0000000000..7e579af217
--- /dev/null
+++ b/docs/en/Migration-Guides/Abp-7_1.md
@@ -0,0 +1,19 @@
+# ABP Version 7.1 Migration Guide
+
+This document is a guide for upgrading ABP v7.0 solutions to ABP v7.1. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application.
+
+## Navigation Menu - `CustomData` type changed to `Dictionary`
+
+`ApplicationMenu` and `ApplicationMenuItem` classes' `CustomData` property type has been changed to `Dictionary`. So, if you use the optional `CustomData` property of these classes, change it accordingly. See [#15608](https://github.com/abpframework/abp/pull/15608) for more information.
+
+*Old usage:*
+
+```csharp
+var menu = new ApplicationMenu("Home", L["Home"], "/", customData: new MyCustomData());
+```
+
+*New usage:*
+
+```csharp
+var menu = new ApplicationMenu("Home", L["Home"], "/").WithCustomData("CustomDataKey", new MyCustomData());
+```
\ No newline at end of file
diff --git a/docs/en/Migration-Guides/IdentityServer4-Step-by-Step.md b/docs/en/Migration-Guides/IdentityServer4-Step-by-Step.md
new file mode 100644
index 0000000000..aff389ccf8
--- /dev/null
+++ b/docs/en/Migration-Guides/IdentityServer4-Step-by-Step.md
@@ -0,0 +1,230 @@
+# Migrating from OpenIddict to IdentityServer4 Step by Step Guide
+
+ABP startup templates use `OpenIddict` OpenID provider from v6.0.0 by default and `IdentityServer` projects are renamed to `AuthServer` in tiered/separated solutions. Since OpenIddict is the default OpenID provider library for ABP templates since v6.0, you may want to keep using [IdentityServer4](https://github.com/IdentityServer/IdentityServer4) library, even it is **archived and no longer maintained by the owners**. ABP doesn't provide support for newer versions of IdentityServer. This guide provides layer-by-layer guidance for migrating your existing [OpenIddict](https://github.com/openiddict/openiddict-core) application to IdentityServer4.
+
+## IdentityServer4 Migration Steps
+
+Use the `abp update` command to update your existing application. See [Upgrading docs](../Upgrading.md) for more info. Apply required migrations by following the [Migration Guides](Index.md) based on your application version.
+
+### Domain.Shared Layer
+
+- In **MyApplication.Domain.Shared.csproj** replace **project reference**:
+
+```csharp
+
+```
+
+ with
+
+```csharp
+
+```
+
+- In **MyApplicationDomainSharedModule.cs** replace usings and **module dependencies:**
+
+```csharp
+using Volo.Abp.OpenIddict;
+...
+typeof(AbpOpenIddictDomainSharedModule)
+```
+
+ with
+
+```csharp
+using Volo.Abp.IdentityServer;
+...
+typeof(AbpIdentityServerDomainSharedModule)
+```
+
+### Domain Layer
+
+- In **MyApplication.Domain.csproj** replace **project references**:
+
+```csharp
+
+
+```
+
+ with
+
+```csharp
+
+
+```
+
+- In **MyApplicationDomainModule.cs** replace usings and **module dependencies**:
+
+```csharp
+using Volo.Abp.OpenIddict;
+using Volo.Abp.PermissionManagement.OpenIddict;
+...
+typeof(AbpOpenIddictDomainModule),
+typeof(AbpPermissionManagementDomainOpenIddictModule),
+```
+
+ with
+
+```csharp
+using Volo.Abp.IdentityServer;
+using Volo.Abp.PermissionManagement.IdentityServer;
+...
+typeof(AbpIdentityServerDomainModule),
+typeof(AbpPermissionManagementDomainIdentityServerModule),
+```
+
+#### OpenIddictDataSeedContributor
+
+DataSeeder is the most important part for starting the application since it seeds the initial data for both OpenID providers.
+
+- Create a folder named *IdentityServer* under the Domain project and copy the [IdentityServerDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.Domain/IdentityServer/IdentityServerDataSeedContributor.cs) under this folder. **Rename** all the `OpenId2Ids` with your project name.
+- Delete *OpenIddict* folder that contains `OpenIddictDataSeedContributor.cs` which is no longer needed.
+
+### EntityFrameworkCore Layer
+
+If you are using MongoDB, skip this step and check the *MongoDB* layer section.
+
+- In **MyApplication.EntityFrameworkCore.csproj** replace **project reference**:
+
+ ```csharp
+
+ ```
+
+ with
+
+ ```csharp
+
+ ```
+
+- In **MyApplicationEntityFrameworkCoreModule.cs** replace usings and **module dependencies**:
+
+```csharp
+using Volo.Abp.OpenIddict.EntityFrameworkCore;
+...
+typeof(AbpOpenIddictEntityFrameworkCoreModule),
+```
+
+ with
+
+```csharp
+using Volo.Abp.IdentityServer.EntityFrameworkCore;
+...
+typeof(AbpIdentityServerEntityFrameworkCoreModule),
+```
+
+- In **MyApplicationDbContext.cs** replace usings and **fluent api configurations**:
+
+ ```csharp
+ using Volo.Abp.OpenIddict.EntityFrameworkCore;
+ ...
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ /* Include modules to your migration db context */
+
+ ...
+ builder.ConfigureOpenIddict();
+ ```
+
+ with
+
+ ```csharp
+ using Volo.Abp.IdentityServer.EntityFrameworkCore;
+ ...
+ using Volo.Abp.OpenIddict.EntityFrameworkCore;
+ ...
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ /* Include modules to your migration db context */
+
+ ...
+ builder.ConfigureIdentityServer();
+ ```
+
+> Not: You need to create new migration after updating the fluent api. Navigate to *EntityFrameworkCore* folder and add a new migration. Ex, `dotnet ef migrations add Updated_To_IdentityServer `
+
+### MongoDB Layer
+
+If you are using EntityFrameworkCore, skip this step and check the *EntityFrameworkCore* layer section.
+
+- In **MyApplication.MongoDB.csproj** replace **project reference**:
+
+ ```csharp
+
+ ```
+
+ with
+
+ ```csharp
+
+ ```
+
+- In **MyApplicationMongoDbModule.cs** replace usings and **module dependencies**:
+
+```csharp
+using Volo.Abp.OpenIddict.MongoDB;
+...
+typeof(AbpOpenIddictMongoDbModule),
+```
+
+ with
+
+```csharp
+using Volo.Abp.IdentityServer.MongoDB;
+...
+typeof(AbpIdentityServerMongoDbModule),
+```
+
+### DbMigrator Project
+
+- In `appsettings.json` **replace OpenIddict section with IdentityServer** since IdentityServerDataSeeder will be using these information for initial data seeding:
+
+ ```json
+ "IdentityServer": { // Rename OpenIddict to IdentityServer
+ "Clients ": { // Rename Applications to Clients
+ ...
+ }
+ }
+ ```
+
+
+### Test Project
+
+- In **MyApplicationTestBaseModule.cs** **add** the IdentityServer related using and PreConfigurations:
+
+ ```csharp
+ using Volo.Abp.IdentityServer;
+ ```
+
+ and
+
+ ```csharp
+ PreConfigure(options =>
+ {
+ options.AddDeveloperSigningCredential = false;
+ });
+
+ PreConfigure(identityServerBuilder =>
+ {
+ identityServerBuilder.AddDeveloperSigningCredential(false, System.Guid.NewGuid().ToString());
+ });
+ ```
+
+ to `PreConfigureServices` to run authentication related unit tests.
+
+### UI Layer
+
+You can follow the migrations guides from IdentityServer to OpenIddict in **reverse order** to update your UIs. You can also check the source-code for [Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/OpenId2Ids/src/OpenId2Ids.AuthServer/Pages/Index.cshtml) and [Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/OpenId2Ids/src/OpenId2Ids.AuthServer/Pages/Index.cshtml.cs) files for **AuthServer** project.
+
+- [Angular UI Migration](OpenIddict-Angular.md)
+- [MVC/Razor UI Migration](OpenIddict-Mvc.md)
+- [Blazor-Server UI Migration](OpenIddict-Blazor-Server.md)
+- [Blazor-Wasm UI Migration](OpenIddict-Blazor.md)
+
+## Source code of samples and module
+
+* [Open source tiered & separate auth server application migrate OpenIddict to Identity Server](https://github.com/abpframework/abp-samples/tree/master/OpenId2Ids)
+* [IdentityServer module document](https://docs.abp.io/en/abp/6.0/Modules/IdentityServer)
+* [IdentityServer module source code](https://github.com/abpframework/abp/tree/rel-6.0/modules/identityserver)
diff --git a/docs/en/Migration-Guides/Index.md b/docs/en/Migration-Guides/Index.md
index 4e6fc6b114..8a693970b2 100644
--- a/docs/en/Migration-Guides/Index.md
+++ b/docs/en/Migration-Guides/Index.md
@@ -2,6 +2,7 @@
The following documents explain how to migrate your existing ABP applications. We write migration documents only if you need to take an action while upgrading your solution. Otherwise, you can easily upgrade your solution using the [abp update command](../Upgrading.md).
+- [7.0 to 7.1](Abp-7_1.md)
- [6.0 to 7.0](Abp-7_0.md)
- [5.3 to 6.0](Abp-6_0.md)
- [5.2 to 5.3](Abp-5_3.md)
diff --git a/docs/en/Migration-Guides/OpenIddict-Angular.md b/docs/en/Migration-Guides/OpenIddict-Angular.md
index c3ca6a7fbb..fe0b3986ac 100644
--- a/docs/en/Migration-Guides/OpenIddict-Angular.md
+++ b/docs/en/Migration-Guides/OpenIddict-Angular.md
@@ -112,7 +112,7 @@
## IdentityServer
-This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refactor and rename your project to *AuthServer* for easier updates in the future.
+This project is renamed to **AuthServer** after v6.0.0. You can also refactor and rename your project to *AuthServer* for easier updates in the future.
- In **MyApplication.IdentityServer.csproj** replace **project references**:
diff --git a/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md b/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md
index 65011f6124..93c9562a4d 100644
--- a/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md
+++ b/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md
@@ -103,7 +103,7 @@
## IdentityServer
-This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refactor and rename your project to *AuthServer* for easier updates in the future.
+This project is renamed to **AuthServer** after v6.0.0. You can also refactor and rename your project to *AuthServer* for easier updates in the future.
- In **MyApplication.IdentityServer.csproj** replace **project references**:
diff --git a/docs/en/Migration-Guides/OpenIddict-Blazor.md b/docs/en/Migration-Guides/OpenIddict-Blazor.md
index 4a3e166cb9..09e137a0a6 100644
--- a/docs/en/Migration-Guides/OpenIddict-Blazor.md
+++ b/docs/en/Migration-Guides/OpenIddict-Blazor.md
@@ -62,7 +62,7 @@
typeof(AbpAccountWebOpenIddictModule),
```
-- In the **MyApplicationBlazorModule.cs** add `PreConfigureServices` like below with your application name as the audience:
+- In the **MyApplicationHostModule.cs** add `PreConfigureServices` like below with your application name as the audience:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
@@ -79,7 +79,7 @@
}
```
-- In the **MyApplicationBlazorModule.cs** `ConfigureServices` method, **replace the method call**:
+- In the **MyApplicationHostModule.cs** `ConfigureServices` method, **replace the method call**:
From `ConfigureAuthentication(context, configuration);` to `ConfigureAuthentication(context);` and update the method as:
@@ -90,7 +90,7 @@
}
```
-- In the **MyApplicationBlazorModule.cs** `OnApplicationInitialization` method, **replace the midware**:
+- In the **MyApplicationHostModule.cs** `OnApplicationInitialization` method, **replace the midware**:
```csharp
app.UseJwtTokenMiddleware();
@@ -131,7 +131,7 @@
## IdentityServer
-This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refactor and rename your project to *AuthServer* for easier updates in the future.
+This project is renamed to **AuthServer** after v6.0.0. You can also refactor and rename your project to *AuthServer* for easier updates in the future.
- In **MyApplication.IdentityServer.csproj** replace **project references**:
diff --git a/docs/en/Migration-Guides/OpenIddict-Mvc.md b/docs/en/Migration-Guides/OpenIddict-Mvc.md
index 7fd411155d..8dd5ec6c94 100644
--- a/docs/en/Migration-Guides/OpenIddict-Mvc.md
+++ b/docs/en/Migration-Guides/OpenIddict-Mvc.md
@@ -111,7 +111,7 @@ Replace role scope to **roles** and add **UsePkce** and **SignoutScheme** option
## IdentityServer
-This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refactor and rename your project to *AuthServer* for easier updates in the future.
+This project is renamed to **AuthServer** after v6.0.0. You can also refactor and rename your project to *AuthServer* for easier updates in the future.
- In **MyApplication.IdentityServer.csproj** replace **project references**:
diff --git a/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md b/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md
index 2638895843..8aff29c84d 100644
--- a/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md
+++ b/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md
@@ -1,6 +1,6 @@
# Migrating from IdentityServer to OpenIddict Step by Step Guide
-This guide provides layer-by-layer guidance for migrating your existing application to [OpenIddict](https://github.com/openiddict/openiddict-core) from IdentityServer. ABP startup templates use `OpenIddict` OpenId provider from v6.0.0-rc1 by default and `IdentityServer` projects are renamed to `AuthServer` in tiered/separated solutions. Since OpenIddict is only available with ABP v6.0, you will need to update your existing application in order to apply OpenIddict changes.
+This guide provides layer-by-layer guidance for migrating your existing application to [OpenIddict](https://github.com/openiddict/openiddict-core) from IdentityServer. ABP startup templates use `OpenIddict` OpenId provider from v6.0.0 by default and `IdentityServer` projects are renamed to `AuthServer` in tiered/separated solutions. Since OpenIddict is only available with ABP v6.0, you will need to update your existing application in order to apply OpenIddict changes.
## History
We are not removing Identity Server packages and we will continue to release new versions of IdentityServer-related NuGet/NPM packages. That means you won't have an issue while upgrading to v6.0 when the stable version releases. We will continue to fix bugs in our packages for a while. ABP 7.0 will be based on .NET 7. If Identity Server continues to work with .NET 7, we will also continue to ship NuGet packages for our IDS integration.
diff --git a/docs/en/Module-Development-Basics.md b/docs/en/Module-Development-Basics.md
index dd01f81ff4..dfce0bf4b2 100644
--- a/docs/en/Module-Development-Basics.md
+++ b/docs/en/Module-Development-Basics.md
@@ -20,7 +20,6 @@ public class BlogModule : AbpModule
{
}
-
````
### Configuring Dependency Injection & Other Modules
diff --git a/docs/en/Module-Entity-Extensions.md b/docs/en/Module-Entity-Extensions.md
index 1a3c318c6b..47436a7dd9 100644
--- a/docs/en/Module-Entity-Extensions.md
+++ b/docs/en/Module-Entity-Extensions.md
@@ -312,12 +312,14 @@ An enum properties is shown as combobox (select) in the create/edit forms:
Enum member name is shown on the table and forms by default. If you want to localize it, just create a new entry on your [localization](https://docs.abp.io/en/abp/latest/Localization) file:
````json
-"UserType.SuperUser": "Super user"
+"Enum:UserType.0": "Super user"
````
One of the following names can be used as the localization key:
+* `Enum:UserType.0`
* `Enum:UserType.SuperUser`
+* `UserType.0`
* `UserType.SuperUser`
* `SuperUser`
diff --git a/docs/en/Modules/Account.md b/docs/en/Modules/Account.md
index 7beb5b128d..2ae77d532d 100644
--- a/docs/en/Modules/Account.md
+++ b/docs/en/Modules/Account.md
@@ -6,7 +6,7 @@ This module is based on [Microsoft's Identity library](https://docs.microsoft.co
## How to Install
-This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
### The Source Code
diff --git a/docs/en/Modules/Audit-Logging.md b/docs/en/Modules/Audit-Logging.md
index 30e1ef4e04..93a50c8387 100644
--- a/docs/en/Modules/Audit-Logging.md
+++ b/docs/en/Modules/Audit-Logging.md
@@ -6,7 +6,7 @@ The Audit Logging Module basically implements the `IAuditingStore` to save the a
## How to Install
-This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
### The Source Code
diff --git a/docs/en/Modules/Background-Jobs.md b/docs/en/Modules/Background-Jobs.md
index 6cce8a6c95..5fd57d812c 100644
--- a/docs/en/Modules/Background-Jobs.md
+++ b/docs/en/Modules/Background-Jobs.md
@@ -6,7 +6,7 @@ The Background Jobs module implements the `IBackgroundJobStore` interface and ma
## How to Install
-This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
### The Source Code
diff --git a/docs/en/Modules/Cms-Kit/Blogging.md b/docs/en/Modules/Cms-Kit/Blogging.md
index 12039b1669..91c14d79e8 100644
--- a/docs/en/Modules/Cms-Kit/Blogging.md
+++ b/docs/en/Modules/Cms-Kit/Blogging.md
@@ -2,6 +2,12 @@
The blogging feature provides the necessary UI to manage and render blogs and blog posts.
+## Enabling the Blogging Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
+
## User Interface
### Menu Items
diff --git a/docs/en/Modules/Cms-Kit/Comments.md b/docs/en/Modules/Cms-Kit/Comments.md
index e5a5da592e..26aa7c0398 100644
--- a/docs/en/Modules/Cms-Kit/Comments.md
+++ b/docs/en/Modules/Cms-Kit/Comments.md
@@ -1,10 +1,16 @@
# CMS Kit: Comments
-CMS kit provides a **comment** system to add comments feature to any kind of resource, like blog posts, products, etc.
+CMS kit provides a **comment** system to add the comment feature to any kind of resource, like blog posts, products, etc.
+
+## Enabling the Comment Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options
-The comment system provides a mechanism to group comment definitions by entity types. For example, if you want to use comment system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product`, and add comments under these entity types.
+The comment system provides a mechanism to group comment definitions by entity types. For example, if you want to use the comment system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product`, and add comments under these entity types.
`CmsKitCommentOptions` can be configured in the domain layer, in the `ConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example:
@@ -12,14 +18,16 @@ The comment system provides a mechanism to group comment definitions by entity t
Configure(options =>
{
options.EntityTypes.Add(new CommentEntityTypeDefinition("Product"));
+ options.IsRecaptchaEnabled = true; //false by default
});
```
-> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
+> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
`CmsKitCommentOptions` properties:
- `EntityTypes`: List of defined entity types(`CmsKitCommentOptions`) in the comment system.
+- `IsRecaptchaEnabled`: This flag enables or disables the reCaptcha for the comment system. You can set it as **true** if you want to use reCaptcha in your comment system.
`CommentEntityTypeDefinition` properties:
@@ -33,11 +41,12 @@ The comment system provides a commenting [widget](../../UI/AspNetCore/Widgets.md
@await Component.InvokeAsync(typeof(CommentingViewComponent), new
{
entityType = "Product",
- entityId = "..."
+ entityId = "...",
+ referralLinks = new [] {"nofollow"}
})
```
-`entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here.
+`entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. `referralLinks` is an optional parameter. You can use this parameter to add values (such as "nofollow", "noreferrer", or any other values) to the [rel attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel) of links.
## User Interface
@@ -77,7 +86,7 @@ A comment represents a written comment from a user.
This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide.
-Following custom repositories are defined for this feature:
+The following custom repositories are defined for this feature:
- `ICommentRepository`
@@ -93,8 +102,8 @@ This module follows the [Domain Services Best Practices & Conventions](https://d
#### Application services
-- `CommentAdminAppService` (implements `ICommentAdminAppService`): Implements the use cases of comment management system, like listing or removing comments etc.
-- `CommentPublicAppService` (implements `ICommentPublicAppService`): Implements the use cases of comment management on the public websites, like listing comments, adding comments etc.
+- `CommentAdminAppService` (implements `ICommentAdminAppService`): Implements the use cases of the comment management system, like listing or removing comments etc.
+- `CommentPublicAppService` (implements `ICommentPublicAppService`): Implements the use cases of the comment management on the public websites, like listing comments, adding comments etc.
### Database providers
diff --git a/docs/en/Modules/Cms-Kit/Global-Resources.md b/docs/en/Modules/Cms-Kit/Global-Resources.md
index 7f639ad884..5e0e0154a0 100644
--- a/docs/en/Modules/Cms-Kit/Global-Resources.md
+++ b/docs/en/Modules/Cms-Kit/Global-Resources.md
@@ -2,6 +2,12 @@
CMS Kit Global Resources system allows to add global styles and scripts dynamically.
+## Enabling the Global Resources Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
+
## The User Interface
### Menu items
diff --git a/docs/en/Modules/Cms-Kit/Index.md b/docs/en/Modules/Cms-Kit/Index.md
index 5b7f3b5366..e40f78922e 100644
--- a/docs/en/Modules/Cms-Kit/Index.md
+++ b/docs/en/Modules/Cms-Kit/Index.md
@@ -16,11 +16,12 @@ The following features are currently available:
* Provides a [**global resources**](Global-Resources.md) system to add global styles and scripts dynamically.
* Provides a [**Dynamic Widget**](Dynamic-Widget.md) system to create dynamic widgets for page and blog posts.
-Click to a feature to understand and learn how to use it.
+> You can click on the any feature links above to understand and learn how to use it.
-All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, by the help of the [Global Features](../../Global-Features.md) system.
+All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, with the help of the [Global Features](../../Global-Features.md) system.
## Pre Requirements
+
- This module depends on [BlobStoring](../../Blob-Storing.md) module for keeping media content.
> Make sure `BlobStoring` module is installed and at least one provider is configured properly. For more information, check the [documentation](../../Blob-Storing.md).
@@ -62,7 +63,7 @@ GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit =>
This module follows the [module development best practices guide](https://docs.abp.io/en/abp/latest/Best-Practices/Index) and consists of several NuGet and NPM packages. See the guide if you want to understand the packages and relations between them.
-CMS kit packages are designed for various usage scenarios. If you check the [CMS kit packages](https://www.nuget.org/packages?q=Volo.CmsKit), you will see that some packages have `Admin` and `Public` suffixes. The reason is that the module has two application layers, considering they might be used in different type of applications. These application layers uses a single domain layer.
+CMS kit packages are designed for various usage scenarios. If you check the [CMS kit packages](https://www.nuget.org/packages?q=Volo.CmsKit), you will see that some packages have `Admin` and `Public` suffixes. The reason is that the module has two application layers, considering they might be used in different type of applications. These application layers uses a single domain layer:
- `Volo.CmsKit.Admin.*` packages contain the functionalities required by admin (back office) applications.
- `Volo.CmsKit.Public.*` packages contain the functionalities used in public websites where users read blog posts or leave comments.
diff --git a/docs/en/Modules/Cms-Kit/Menus.md b/docs/en/Modules/Cms-Kit/Menus.md
index 87486326da..b469c322ab 100644
--- a/docs/en/Modules/Cms-Kit/Menus.md
+++ b/docs/en/Modules/Cms-Kit/Menus.md
@@ -1,7 +1,13 @@
-# CMS Kit: Pages
+# CMS Kit: Menus
CMS Kit Menu system allows to manage public menus dynamically.
+## Enabling the Menu Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
+
## The User Interface
### Menu items
@@ -20,22 +26,18 @@ Menus page is used to manage dynamic public menus in the system.

-
-
-Created menus will be visible on public site.
+The created menu items will be visible on the public-web side, as shown below:

-# Internals
+## Internals
-## Domain Layer
+### Domain Layer
#### Aggregates
This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide.
-##### Menus
-
- `MenuItem` (aggregate root): A Menu Item presents a single node at menu tree.
#### Repositories
diff --git a/docs/en/Modules/Cms-Kit/Pages.md b/docs/en/Modules/Cms-Kit/Pages.md
index 7945bfd5ff..cc1f5074f4 100644
--- a/docs/en/Modules/Cms-Kit/Pages.md
+++ b/docs/en/Modules/Cms-Kit/Pages.md
@@ -2,6 +2,12 @@
CMS Kit Page system allows you to create dynamic pages by specifying URLs, which is the fundamental feature of a CMS.
+## Enabling the Pages Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
+
## The User Interface
### Menu items
@@ -24,5 +30,5 @@ You can create or edit an existing page on this page.

-When you create a page, you can access the created page via `/pages/{slug}` URL.
+When you create a page, you can access the created page via `/{slug}` URL.
diff --git a/docs/en/Modules/Cms-Kit/Ratings.md b/docs/en/Modules/Cms-Kit/Ratings.md
index 8702feb24e..db58602c15 100644
--- a/docs/en/Modules/Cms-Kit/Ratings.md
+++ b/docs/en/Modules/Cms-Kit/Ratings.md
@@ -4,6 +4,12 @@ CMS kit provides a **rating** system to to add ratings feature to any kind of re

+## Enabling the Rating Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
+
## Options
The rating system provides a mechanism to group ratings by entity types. For example, if you want to use the rating system for products, you need to define an entity type named `Product` and then add ratings under the defined entity type.
@@ -17,7 +23,7 @@ Configure(options =>
});
```
-> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
+> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
`CmsKitRatingOptions` properties:
diff --git a/docs/en/Modules/Cms-Kit/Reactions.md b/docs/en/Modules/Cms-Kit/Reactions.md
index 86da9ccb72..457d5e0f26 100644
--- a/docs/en/Modules/Cms-Kit/Reactions.md
+++ b/docs/en/Modules/Cms-Kit/Reactions.md
@@ -8,6 +8,12 @@ Reaction component allows users to react to your content via pre-defined icons/e
You can also customize the reaction icons shown in the reaction component.
+## Enabling the Reaction Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
+
## Options
Reaction system provides a mechanism to group reactions by entity types. For example, if you want to use the reaction system for products, you need to define an entity type named `Product`, and then add reactions under the defined entity type.
@@ -32,7 +38,7 @@ Configure(options =>
});
```
-> If you're using the comment or blog features, the ABP framework defines predefined reactions for these features automatically.
+> If you're using the [Comment](Comments.md) or [Blogging](Blogging.md) features, the ABP framework defines predefined reactions for these features automatically.
`CmsKitReactionOptions` properties:
diff --git a/docs/en/Modules/Cms-Kit/Tags.md b/docs/en/Modules/Cms-Kit/Tags.md
index 17bc5cf47f..8f91bc67c7 100644
--- a/docs/en/Modules/Cms-Kit/Tags.md
+++ b/docs/en/Modules/Cms-Kit/Tags.md
@@ -2,6 +2,12 @@
CMS kit provides a **tag** system to tag any kind of resources, like a blog post.
+## Enabling the Tag Management Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
+
## Options
The tag system provides a mechanism to group tags by entity types. For example, if you want to use the tag system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product` and add tags under these entity types.
@@ -17,7 +23,7 @@ Configure(options =>
});
```
-> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically.
+> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically.
`CmsKitTagOptions` properties:
diff --git a/docs/en/Modules/Database-Tables.md b/docs/en/Modules/Database-Tables.md
new file mode 100644
index 0000000000..8d0290b311
--- /dev/null
+++ b/docs/en/Modules/Database-Tables.md
@@ -0,0 +1,575 @@
+# Database Tables
+
+This documentation describes all database tables and their purposes. You can read this documentation to get general knowledge of the database tables that come from each module.
+
+## [Audit Logging Module](Audit-Logging.md)
+
+### AbpAuditLogs
+
+This table stores information about the audit logs in the application. Each record represents an audit log and tracks the actions performed in the application.
+
+### AbpAuditLogActions
+
+This table stores information about the actions performed in the application, which are logged for auditing purposes.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpAuditLogs](#abpauditlogs) | Id | Links each action to a specific audit log. |
+
+### AbpEntityChanges
+
+This table stores information about entity changes in the application, which are logged for auditing purposes.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpAuditLogs](#abpauditlogs) | Id | Links each entity change to a specific audit log. |
+
+### AbpEntityPropertyChanges
+
+This table stores information about property changes to entities in the application, which are logged for auditing purposes.
+
+## Uses
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpEntityChanges](#abpentitychanges) | Id | Links each property change to a specific entity change. |
+
+## [Background Jobs Module](Background-Jobs.md)
+
+### AbpBackgroundJobs
+
+This table stores information about the background jobs in the application and facilitates their efficient management and tracking. Each entry in the table contains details of a background job, including the job name, arguments, try count, next try time, last try time, abandoned status, and priority.
+
+## [Tenant Management Module](Tenant-Management.md)
+
+### AbpTenants
+
+This table stores information about the tenants. Each record represents a tenant and contains information about the tenant, such as name and other details.
+
+### AbpTenantConnectionStrings
+
+This table stores information about the tenant database connection strings. When you define a connection string for a tenant, a new record will be added to this table. You can query this database to get connection strings by tenants.
+
+## Uses
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpTenants](#abptenants) | Id | The `Id` column in the `AbpTenants` table is used to associate the tenant connection string with the corresponding tenant. |
+
+## Blogging Module
+
+### BlgUsers
+
+This table stores information about the blog users. When a new identity user is created, a new record will be added to this table.
+
+### BlgBlogs
+
+This table serves to store blog information and semantically separates the posts of each blog.
+
+### BlgPosts
+
+This table stores information about the blog posts. You can query this table to get blog posts by blogs.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [BlgBlogs](#blgblogs) | Id | To associate the blog post with the corresponding blog. |
+### BlgComments
+
+This table stores information about the comments made on blog posts. You can query this table to get comments by posts.
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [BlgPosts](#blgposts) | Id | Links the comment to the corresponding blog post. |
+| [BlgComments](#blgcomments) | Id | Links the comment to the parent comment. |
+
+### BlgTags
+
+This table stores information about the tags. When a new tag is used, a new record will be added to this table. You can query this table to get tags by blogs.
+
+### BlgPostTags
+
+This table is used to associate tags with blog posts in order to categorize and organize the content. You can query this table to get post tags by posts.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [BlgTags](#blgtags) | Id | Links the post tag to the corresponding tag. |
+| [BlgPosts](#blgposts) | Id | Links the post tag to the corresponding blog post. |
+
+## [CMS Kit Module](Cms-Kit/Index.md)
+
+### CmsUsers
+
+This table stores information about the CMS Kit module users. When a new identity user is created, a new record will be added to this table.
+
+### CmsBlogs
+
+This table serves to store blog information and semantically separates the posts of each blog.
+
+### CmsBlogPosts
+
+This table stores information about the blog posts. You can query this table to get blog posts by blogs.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [CmsUsers](#cmsusers) | Id | Links the blog post to the corresponding author. |
+
+### CmsBlogFeatures
+
+This table stores information about the blog features. You can query this table to get blog features by blogs.
+
+### CmsComments
+
+This table is utilized by the [CMS Kit Comment system](Cms-Kit/Comments.md) to store comments made on the blog posts. You can query this table to get comments by posts.
+
+### CmsTags
+
+This table stores information about the tags. When a new tag is used, a new record will be added to this table. You can query this table to get tags by blogs.
+
+### CmsEntityTags
+
+This table is utilized by the [Tag Management system](Cms-Kit/Tags.md) to store tags and their relationship with various entities, thus enabling efficient categorization and organization of content. You can query this table to get entity tags by entities.
+
+### CmsGlobalResources
+
+This table is a database table for the [CMS Kit Global Resources system](Cms-Kit/Global-Resources.md), allowing dynamic addition of global styles and scripts.
+
+### CmsMediaDescriptors
+
+This table is utilized by the CMS kit module to manage media files by using the [BlobStoring](../Blob-Storing.md) module.
+
+### CmsMenuItems
+
+This table is used by the [CMS Kit Menu system](Cms-Kit/Menus.md) to manage and store information about dynamic public menus, including details such as menu item display names, URLs, and hierarchical relationships.
+
+### CmsPages
+
+This table is utilized by the [CMS Kit Page system](Cms-Kit/Pages.md) to store dynamic pages within the application, including information such as page URLs, titles, and content.
+
+### CmsRatings
+
+This table is utilized by the [CMS Kit Rating system](Cms-Kit/Ratings.md) to store ratings made on blog posts. You can query this table to get ratings by posts.
+
+### CmsUserReactions
+
+This table is utilized by the [CMS Kit Reaction system](Cms-Kit/Reactions.md) to store reactions made on blog posts. You can query this table to get reactions by posts.
+
+## [Docs Module](Docs.md)
+
+### DocsProjects
+
+This table stores project information to categorize documents according to different projects.
+
+### DocsDocuments
+
+This table retrieves the document if it's not found in the cache. The documentation is being updated when the content is retrieved from the database.
+
+### DocsDocumentContributors
+
+This table stores information about the contributors of the documents. You can query this table to get document contributors by documents.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [DocsDocuments](#docsdocuments) | Id | Links the document contributor to the corresponding document. |
+
+## [Feature Management Module](Feature-Management.md)
+
+### AbpFeatureGroups
+
+This table stores information about the feature groups in the application. For example, you can group all the features in the [`AbpFeatures`](#abpfeatures) table related to the `Identity` module under the `Identity` group.
+
+### AbpFeatures
+
+This table stores information about the features in the application. You can use the `Name` column to link each feature with its corresponding feature value in the [`AbpFeatureValues`](#abpfeaturevalues) table, so that you can easily manage and organize the features.
+
+### AbpFeatureValues
+
+This table stores the values of the features for different providers. You can use the `Name` column to link each feature value with its corresponding feature in the [`AbpFeatures`](#abpfeatures) table, so that you can easily manage and organize the features.
+
+## [Identity Module](Identity.md)
+
+### AbpUsers
+
+This table stores information about the identity users in the application.
+
+### AbpRoles
+
+This table stores information about the roles in the application. Roles are used to manage and control access to different parts of the application by assigning permissions and claims to roles and then assigning those roles to users. This table is important for managing and organizing the roles in the application, and for defining the access rights of the users.
+
+### AbpClaimTypes
+
+This table stores information about the claim types used in the application. You can use the `Name`, `Regex` columns to filter the claim types by name, and regex pattern respectively, so that you can easily manage and track the claim types in the application.
+
+### AbpLinkUsers
+
+This table is useful for linking multiple user accounts across different tenants or applications to a single user, allowing them to easily switch between their accounts.
+
+### AbpUserClaims
+
+This table can manage user-based access control by allowing to assign claims to users, which describes the access rights of the individual user.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpUsers](#abpusers) | Id | Links the user claim to the corresponding user. |
+
+### AbpUserLogins
+
+This table can store information about the user's external logins such as login with Facebook, Google, etc. and it can also be used to track the login history of users.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpUsers](#abpusers) | Id | Links the user login to the corresponding user. |
+
+### AbpUserRoles
+
+This table can manage user-based access control by allowing to assign roles to users, which describe the access rights of the individual user.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpUsers](#abpusers) | Id | Links the user role to the corresponding user. |
+| [AbpRoles](#abproles) | Id | Links the user role to the corresponding role. |
+
+### AbpUserTokens
+
+This table can store information about user's refresh tokens, access tokens and other tokens used in the application. It can also be used to invalidate or revoke user tokens.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpUsers](#abpusers) | Id | Links the user token to the corresponding user. |
+
+### AbpOrganizationUnits
+
+This table is useful for creating and managing a hierarchical structure of the organization, allowing to group users and assign roles based on the organization structure. You can use the `Code`, `ParentId` columns to filter the organization units by code and parent id respectively, so that you can easily manage and track the organization units in the application.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpOrganizationUnits](#abporganizationunits) | ParentId | Links the organization unit to its parent organization unit. |
+
+### AbpOrganizationUnitRoles
+
+This table is useful for managing role-based access control at the level of organization units, allowing to assign different roles to different parts of the organization structure. You can use the `OrganizationUnitId`, `RoleId` columns to filter the roles by organization unit id and role id respectively, so that you can easily manage and track the roles assigned to organization units in the application.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpOrganizationUnits](#abporganizationunits) | Id | Links the organization unit role to the corresponding organization unit. |
+| [AbpRoles](#abproles) | Id | Links the organization unit role to the corresponding role. |
+
+### AbpUserOrganizationUnits
+
+This table stores information about the organization units assigned to the users in the application. This table can manage user-organization unit relationships, and to group users based on the organization structure.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpUsers](#abpusers) | Id | Links the user organization unit to the corresponding user. |
+| [AbpOrganizationUnits](#abporganizationunits) | Id | Links the user organization unit to the corresponding organization unit. |
+
+### AbpRoleClaims
+
+This table is useful for managing role-based access control by allowing to assign claims to roles, which describes the access rights of the users that belong to that role.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpRoles](#abproles) | Id | Links the role claim to the corresponding role. |
+
+### AbpSecurityLogs
+
+This table logs important operations and changes related to user accounts, allowing users to save the security logs for future reference.
+
+## [Permission Management](Permission-Management.md)
+
+### AbpPermissionGroups
+
+This table is important for managing and organizing the permissions in the application, by grouping them into logical categories.
+
+### AbpPermissions
+
+This table is important for managing and controlling access to different parts of the application and for defining the granular permissions that make up the larger permissions or roles.
+
+### AbpPermissionGrants
+
+The table stores and manage the permissions in the application and to keep track of permissions that are granted, to whom and when. Columns such as `Name`, `ProviderName`, `ProviderKey`, `TenantId` can be used to filter the granted permissions by name, provider name, provider key, and tenant id respectively, so that you can easily manage and track the granted permissions in the application.
+
+## [Setting Management](Setting-Management.md)
+
+### AbpSettings
+
+This table stores key-value pairs of settings for the application, and it allows dynamic configuration of the application without the need for recompilation.
+
+## [OpenIddict](OpenIddict.md)
+
+### OpenIddictApplications
+
+This table can store information about the OpenID Connect applications, including the client id, client secret, redirect URI, and other relevant information. It can also be used to authenticate and authorize clients using OpenID Connect protocol.
+
+### OpenIddictAuthorizations
+
+This table stores the OpenID Connect authorization data in the application. It can also be used to manage and validate the authorization grants issued to clients and users.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [OpenIddictApplications](#openiddictapplications) | Id | Links the authorization to the corresponding application. |
+
+### OpenIddictTokens
+
+This table can store information about the OpenID Connect tokens, including the token payload, expiration, type, and other relevant information. It can also be used to manage and validate the tokens issued to clients and users, such as access tokens and refresh tokens, and to control access to protected resources.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [OpenIddictApplications](#openiddictapplications) | Id | Links the token to the corresponding application. |
+| [OpenIddictAuthorizations](#openiddictauthorizations) | Id | Links the token to the corresponding authorization. |
+
+### OpenIddictScopes
+
+This table can store information about the OpenID Connect scopes, including the name and description of the scope. It can also be used to define the permissions or access rights associated with the scopes, which are then used to control access to protected resources.
+
+## [IdentityServer](IdentityServer.md)
+
+### IdentityServerApiResources
+
+This table can store information about the API resources, including the resource name, display name, description, and other relevant information. It can also be used to define the scopes, claims, and properties associated with the API resources, which are then used to control access to protected resources.
+
+### IdentityServerIdentityResources
+
+This table can store information about the identity resources, including the name, display name, description, and enabled status.
+
+### IdentityServerClients
+
+This table can store information about the clients, including the client id, client name, client URI and other relevant information. It can also be used to define the scopes, claims, and properties associated with the clients, which are then used to control access to protected resources.
+
+### IdentityServerApiScopes
+
+This table can store information about the API scopes, including the scope name, display name, description, and other relevant information. It can also be used to define the claims and properties associated with the API scopes, which are then used to control access to protected resources.
+
+### IdentityServerApiResourceClaims
+
+This table can store information about the claims of an API resource, including the claim type and API resource id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the claim to the corresponding API resource. |
+
+### IdentityServerIdentityResourceClaims
+
+This table can store information about the claims of an identity resource, including the claim type and identity resource id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerIdentityResources](#identityserveridentityresources) | Id | Links the claim to the corresponding identity resource. |
+
+### IdentityServerClientClaims
+
+This table can store information about the claims of a client, including the claim type, claim value and client id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the claim to the corresponding client. |
+
+### IdentityServerApiScopeClaims
+
+This table can store information about the claims of an API scope, including the claim type and API scope id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerApiScopes](#identityserverapiscopes) | Id | Links the claim to the corresponding API scope. |
+
+### IdentityServerApiResourceProperties
+
+This table can store information about properties, including the property key and value, and the associated API resource. These properties can store additional metadata or configuration information related to the API resources.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the property to the corresponding API resource. |
+
+### IdentityServerIdentityResourceProperties
+
+This table can store information about properties, including the property key and value, and the associated identity resource. These properties can store additional metadata or configuration information related to the identity resources.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerIdentityResources](#identityserveridentityresources) | Id | Links the property to the corresponding identity resource. |
+
+### IdentityServerClientProperties
+
+This table can be store information about the properties of a client, including the key, value and client id. These properties can store additional metadata or configuration information related to the clients.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the property to the corresponding client. |
+
+### IdentityServerApiScopeProperties
+
+This table can store information about the properties of an API scope, including the key, value and API scope id. These properties can store additional metadata or configuration information related to the API scopes.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerApiScopes](#identityserverapiscopes) | Id | Links the property to the corresponding API scope. |
+
+### IdentityServerApiResourceScopes
+
+This table can store information about the scopes of an API resource, including the scope name and API resource id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the scope to the corresponding API resource. |
+
+### IdentityServerClientScopes
+
+ This table can store information about the scopes of a client, including the scope and client id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the scope to the corresponding client. |
+
+### IdentityServerApiResourceSecrets
+
+This table can store information about the secrets of an API resource, including the secret value, expiration date, and API resource id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the secret to the corresponding API resource. |
+
+### IdentityServerClientSecrets
+
+This table can store information about the secrets of a client, including the secret value, expiration date, and client id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the secret to the corresponding client. |
+
+### IdentityServerClientCorsOrigins
+
+This table can store information about the CORS origins of a client, including the origin and client id. It can also be used to manage and validate the CORS origins of a client.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the CORS origin to the corresponding client. |
+
+### IdentityServerClientGrantTypes
+
+This table can store information about the grant types of a client, including the grant type and client id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the grant type to the corresponding client. |
+
+### IdentityServerClientIdPRestrictions
+
+This table can store information about the identity provider restrictions of a client, including the identity provider and client id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the identity provider restriction to the corresponding client. |
+
+### IdentityServerClientPostLogoutRedirectUris
+
+This table can store information about the post logout redirect URIs of a client, including the post logout redirect URI and client id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the post logout redirect URI to the corresponding client. |
+
+### IdentityServerClientRedirectUris
+
+This table can store information about the redirect URIs of a client, including the redirect URI and client id.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [IdentityServerClients](#identityserverclients) | Id | Links the redirect URI to the corresponding client. |
+
+### IdentityServerDeviceFlowCodes
+
+This table can store information about the device flow codes, including the user code, device code, subject id, client id, creation time, expiration, data and session id.
+
+### IdentityServerPersistedGrants
+
+This table can store information about the persisted grants, including the key, type, subject id, client id, creation time, expiration, and data.
+
+## Others
+
+### AbpBlobContainers
+
+This table is important for providing a better user experience by allowing the application to support multiple containers and providing BLOB-specific features.
+
+### AbpBlobs
+
+This table stores the binary data of BLOBs (binary large objects) in the application. Each BLOB is related to a container in the [AbpBlobContainers](#abpblobcontainers) table, where the container name, tenant id and other properties of the container can be found.
+
+#### Foreign Keys
+
+| Table | Column | Description |
+| --- | --- | --- |
+| [AbpBlobContainers](#abpblobcontainers) | Id | Links the BLOB to the corresponding container. |
+
+### AbpLocalizationResources
+
+This table stores the localization resources for the application. This table is important for providing a better user experience by allowing the application to support multiple resources and providing localized text and other localization-specific features.
+
+### AbpLocalizationTexts
+
+The table contains the resource name, culture name, and a json encoded value which holds the key-value pair of localization text. It allows for efficient storage and management of localization texts and allows for easy update or addition of new translations for specific resources and cultures.
diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md
index 4777275cd6..143ec7168d 100644
--- a/docs/en/Modules/Docs.md
+++ b/docs/en/Modules/Docs.md
@@ -28,9 +28,11 @@ If you do not have an existing ABP project, this step shows you how to create a
It is recommended to use ABP CLI to create new projects. Use the following command:
-`abp new Acme.MyProject`
+```bash
+abp new Acme.MyProject
+```
-You can also navigate to https://abp.io/get-started. Enter your project name as `Acme.MyProject`, other use default options.
+You can also generate a CLI command from [get started page](https://abp.io/get-started). Enter your project name as `Acme.MyProject`, other use default options.
Note that this document covers `Entity Framework Core` provider but you can also select `MongoDB` as your database provider.
@@ -66,7 +68,9 @@ Docs module packages are hosted on NuGet. There are 4 packages that needs be to
It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command:
-`abp add-module Volo.Docs`
+```bash
+abp add-module Volo.Docs
+```
#### 3.2- Manually install
@@ -74,19 +78,27 @@ Or you can also manually install nuget package to each project:
* Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project.
- `Install-Package Volo.Docs.Domain`
+ ```bash
+ Install-Package Volo.Docs.Domain
+ ```
* Install [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) nuget package to `Acme.MyProject.EntityFrameworkCore` project.
- `Install-Package Volo.Docs.EntityFrameworkCore`
+ ```bash
+ Install-Package Volo.Docs.EntityFrameworkCore
+ ```
* Install [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) nuget package to `Acme.MyProject.Application` project.
- `Install-Package Volo.Docs.Application`
+ ```bash
+ Install-Package Volo.Docs.Application
+ ```
* Install [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Web` project.
- `Install-Package Volo.Docs.Web`
+ ```bash
+ Install-Package Volo.Docs.Web
+ ```
##### 3.2.1- Adding Module Dependencies
@@ -258,7 +270,7 @@ If you choose Entity Framework as your database provider, you need to configure
The default route for Docs module is;
-```
+```txt
/Documents
```
@@ -307,7 +319,7 @@ The new menu item for Docs Module is added to the menu. Run your web application
You will see a warning says;
-```
+```txt
There are no projects yet!
```
@@ -487,14 +499,14 @@ For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/bl
```
.....
-````json
+```json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
-````
+```
........
```
@@ -530,9 +542,9 @@ For example:
You can also use variables in a text, adding **_Value** postfix to its key:
-````
+```txt
This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider.
-````
+```
Also, **Document_Language_Code** and **Document_Version** keys are pre-defined if you want to get the language code or the version of the current document (This may be useful for creating links that redirects to another documentation system in another domain).
@@ -603,7 +615,7 @@ Finally a new Docs Module is added to your project which is feeded with GitHub.
The Docs module supports full-text search using Elastic Search. It is not enabled by default. You can configure `DocsElasticSearchOptions` to enable it.
-```
+```csharp
Configure(options =>
{
options.Enable = true;
@@ -616,7 +628,7 @@ The `Index` is automatically created after the application starts if the `Index`
`DefaultElasticClientProvider` is responsible for creating `IElasticClient`. By default, it reads Elastic Search's `Url` from `IConfiguration`.
If your `IElasticClient` needs additional configuration, please use override `IElasticClientProvider` service and replace it in the [dependency injection](../Dependency-Injection.md) system.
-```
+```json
{
"ElasticSearch": {
"Url": "http://localhost:9200"
diff --git a/docs/en/Modules/Feature-Management.md b/docs/en/Modules/Feature-Management.md
index 9677e2f470..d6cf470391 100644
--- a/docs/en/Modules/Feature-Management.md
+++ b/docs/en/Modules/Feature-Management.md
@@ -6,7 +6,7 @@ The Feature Management module implements the `IFeatureManagementStore` interface
## How to Install
-This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
### The Source Code
diff --git a/docs/en/Modules/Identity.md b/docs/en/Modules/Identity.md
index 4cd8bef316..f73e8073b2 100644
--- a/docs/en/Modules/Identity.md
+++ b/docs/en/Modules/Identity.md
@@ -4,7 +4,7 @@ Identity module is used to manage roles, users and their permissions, based on t
## How to Install
-This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
### The Source Code
diff --git a/docs/en/Modules/IdentityServer.md b/docs/en/Modules/IdentityServer.md
index 90e9fe19eb..90c08f6c6b 100644
--- a/docs/en/Modules/IdentityServer.md
+++ b/docs/en/Modules/IdentityServer.md
@@ -1,10 +1,12 @@
# IdentityServer Module
-IdentityServer module provides a full integration with the [IdentityServer](https://github.com/IdentityServer/IdentityServer4) (IDS) framework, which provides advanced authentication features like single sign-on and API access control. This module persists clients, resources and other IDS-related objects to database.
+IdentityServer module provides a full integration with the [IdentityServer4](https://github.com/IdentityServer/IdentityServer4) (IDS) framework, which provides advanced authentication features like single sign-on and API access control. This module persists clients, resources and other IDS-related objects to database. **This module is replaced by** [OpenIddict module](https://docs.abp.io/en/abp/latest/Modules/OpenIddict) after ABP v6.0 in the startup templates.
+
+> Note: You can not use IdentityServer and OpenIddict modules together. They are separate OpenID provider libraries for the same job.
## How to Install
-This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+You don't need this module when you are using OpenIddict module. However, if you want to keep using IdentityServer4 for your applications, you can install this module and remove the OpenIddict module. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
### The Source Code
diff --git a/docs/en/Modules/OpenIddict.md b/docs/en/Modules/OpenIddict.md
index 3a015011e6..cfa35b7e2c 100644
--- a/docs/en/Modules/OpenIddict.md
+++ b/docs/en/Modules/OpenIddict.md
@@ -4,7 +4,7 @@ OpenIddict module provides an integration with the [OpenIddict](https://github.c
## How to Install
-This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as a package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as a package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
### The Source Code
@@ -299,12 +299,12 @@ PreConfigure(options =>
The background task that automatically removes orphaned tokens/authorizations. This can be configured by `TokenCleanupOptions` to manage it.
-`TokenCleanupOptions` can be configured in the `PreConfigureServices` method of your OpenIddict [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics).
+`TokenCleanupOptions` can be configured in the `ConfigureServices` method of your OpenIddict [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics).
Example:
```csharp
-PreConfigure(options =>
+Configure(options =>
{
//Set options here...
});
diff --git a/docs/en/Modules/Permission-Management.md b/docs/en/Modules/Permission-Management.md
index b97ddfca6c..e77a284a42 100644
--- a/docs/en/Modules/Permission-Management.md
+++ b/docs/en/Modules/Permission-Management.md
@@ -6,7 +6,7 @@ This module implements the `IPermissionStore` to store and manage permissions va
## How to Install
-This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
### The Source Code
diff --git a/docs/en/MongoDB.md b/docs/en/MongoDB.md
index d99c40da43..bfdac23104 100644
--- a/docs/en/MongoDB.md
+++ b/docs/en/MongoDB.md
@@ -393,7 +393,7 @@ One advantage of using interface for a MongoDbContext is then it becomes replace
Once you properly define and use an interface for a MongoDbContext , then any other implementation can use the following ways to replace it:
-**ReplaceDbContextAttribute**
+#### ReplaceDbContext Attribute
```csharp
[ReplaceDbContext(typeof(IBookStoreMongoDbContext))]
@@ -403,7 +403,7 @@ public class OtherMongoDbContext : AbpMongoDbContext, IBookStoreMongoDbContext
}
```
-**ReplaceDbContext option**
+#### ReplaceDbContext Option
```csharp
context.Services.AddMongoDbContext(options =>
@@ -415,6 +415,22 @@ context.Services.AddMongoDbContext(options =>
In this example, `OtherMongoDbContext` implements `IBookStoreMongoDbContext`. This feature allows you to have multiple MongoDbContext (one per module) on development, but single MongoDbContext (implements all interfaces of all MongoDbContexts) on runtime.
+#### Replacing with Multi-Tenancy
+
+It is also possible to replace a DbContext based on the [multi-tenancy](Multi-Tenancy.md) side. `ReplaceDbContext` attribute and `ReplaceDbContext` method can get a `MultiTenancySides` option with a default value of `MultiTenancySides.Both`.
+
+**Example:** Replace DbContext only for tenants, using the `ReplaceDbContext` attribute
+
+````csharp
+[ReplaceDbContext(typeof(IBookStoreDbContext), MultiTenancySides.Tenant)]
+````
+
+**Example:** Replace DbContext only for the host side, using the `ReplaceDbContext` method
+
+````csharp
+options.ReplaceDbContext(MultiTenancySides.Host);
+````
+
### Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IMongoDbBulkOperationProvider`.
diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md
index a1a5ac5cce..23ed2371dd 100644
--- a/docs/en/Repositories.md
+++ b/docs/en/Repositories.md
@@ -164,6 +164,14 @@ If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method
> See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
+### Delete Direct
+
+`DeleteDirectAsync` method of the repository deletes all entities those fit to the given predicate. It directly deletes entities from database, without fetching them.
+
+Some features (like soft-delete, multi-tenancy and audit logging) won't work, so use this method carefully when you need it. Use the `DeleteAsync` method if you need to these features.
+
+> Currently only [EF Core supports it](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#basic-executedelete-examples), For the ORMs doesn't support direct delete, we will fallback to the existing `DeleteAsync` method.
+
### Ensure Entities Exists
The `EnsureExistsAsync` extension method accepts entity id or entities query expression to ensure entities exist, otherwise, it will throw `EntityNotFoundException`.
diff --git a/docs/en/Road-Map.md b/docs/en/Road-Map.md
index 45f33dbd9c..7cf8948515 100644
--- a/docs/en/Road-Map.md
+++ b/docs/en/Road-Map.md
@@ -6,7 +6,7 @@ This document provides a road map, release schedule and planned features for the
### v7.1
-The next version will be 7.1 and planned to release the stable 7.1 version in February, 2024. In the version 7.1, we will mostly focus on stabilizing and enhancing existing features, improving the developer experience, as well as adding relatively minor new features.
+The next version will be 7.1 and planned to release the stable 7.1 version in February, 2023. In the version 7.1, we will mostly focus on stabilizing and enhancing existing features, improving the developer experience, as well as adding relatively minor new features.
See the [7.1 milestone](https://github.com/abpframework/abp/milestone/68) for all the issues we've planned to work on.
diff --git a/docs/en/Startup-Templates/Application-Single-Layer.md b/docs/en/Startup-Templates/Application-Single-Layer.md
index 95ebad55a7..02a743c5ee 100644
--- a/docs/en/Startup-Templates/Application-Single-Layer.md
+++ b/docs/en/Startup-Templates/Application-Single-Layer.md
@@ -10,7 +10,7 @@ ABP's [Application Startup Template](Application.md) provides a well-organized a
## How to Start with It?
-You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can directly create & download this startup template from the [Get Started](https://abp.io/get-started) page. In this section, we will use the ABP CLI.
+You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can generate a CLI command for this startup template from the [Get Started](https://abp.io/get-started) page. In this section, we will use the ABP CLI.
Firstly, install the ABP CLI if you haven't installed it before:
@@ -32,19 +32,18 @@ abp new Acme.BookStore -t app-nolayers
This template provides multiple UI frameworks:
* `mvc`: ASP.NET Core MVC UI with Razor Pages (default)
+* `blazor`: Blazor UI
* `blazor-server`: Blazor Server UI
* `angular`: Angular UI
* `none`: Without UI (for HTTP API development)
-> This template doesn't have Blazor WebAssembly UI, because it requires 3 projects at least (server-side, UI and shared library between these two projects). We are recommending to use the layered [application startup template](Application.md) for Blazor WebAssembly projects.
-
Use the `-u` (or `--ui`) option to specify the UI framework while creating the solution:
```bash
abp new Acme.BookStore -t app-nolayers -u angular
```
-This example specifies the UI type (the `-u` option) as `angular`. You can also specify `mvc`, `blazor-server` or `none` for the UI type.
+This example specifies the UI type (the `-u` option) as `angular`. You can also specify `mvc`, `blazor`, `blazor-server` or `none` for the UI type.
### Specify the Database Provider
diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md
index 6a47363e8c..98ccdd10d2 100644
--- a/docs/en/Startup-Templates/Application.md
+++ b/docs/en/Startup-Templates/Application.md
@@ -11,7 +11,7 @@ This document explains **the solution structure** and projects in details. If yo
## How to Start With?
-You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can directly create & download from the [Get Started](https://abp.io/get-started) page. CLI approach is used here.
+You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can generate a CLI command from the [Get Started](https://abp.io/get-started) page. CLI approach is used here.
First, install the ABP CLI if you haven't installed it before:
diff --git a/docs/en/Startup-Templates/Module.md b/docs/en/Startup-Templates/Module.md
index afb6496ed5..faf4cb41a2 100644
--- a/docs/en/Startup-Templates/Module.md
+++ b/docs/en/Startup-Templates/Module.md
@@ -4,7 +4,7 @@ This template can be used to create a **reusable [application module](../Modules
## How to Start With?
-You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can directly create & download from the [Get Started](https://abp.io/get-started) page. CLI approach is used here.
+You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can generate a CLI command from the [Get Started](https://abp.io/get-started) page. CLI approach is used here.
First, install the ABP CLI if you haven't installed before:
diff --git a/docs/en/Themes/LeptonXLite/Angular.md b/docs/en/Themes/LeptonXLite/Angular.md
index 950cd20c8b..56b003e606 100644
--- a/docs/en/Themes/LeptonXLite/Angular.md
+++ b/docs/en/Themes/LeptonXLite/Angular.md
@@ -14,13 +14,19 @@ To add `LeptonX-lite` into your project,
- Install `@abp/ng.theme.lepton-x`
-`yarn add @abp/ng.theme.lepton-x@preview`
+```bash
+yarn add @abp/ng.theme.lepton-x
+```
- Install `bootstrap-icons`
-`yarn add bootstrap-icons`
+```bash
+yarn add bootstrap-icons
+```
+
+- Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one in the following link :
-- Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one.
+* [Styles - Angular UI](../../UI/Angular/Theme-Configurations.md)
Add the following style
diff --git a/docs/en/Themes/LeptonXLite/AspNetCore.md b/docs/en/Themes/LeptonXLite/AspNetCore.md
index f480141120..b134135a04 100644
--- a/docs/en/Themes/LeptonXLite/AspNetCore.md
+++ b/docs/en/Themes/LeptonXLite/AspNetCore.md
@@ -38,7 +38,7 @@ Configure(options =>
// Remove the following line
- BasicThemeBundles.Styles.Global,
// Add the following line instead
-+ LeptonXLiteThemeBundles.Styles.Global
++ LeptonXLiteThemeBundles.Styles.Global,
bundle =>
{
bundle.AddFiles("/global-styles.css");
diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md
index eb00a94e0c..0b50992274 100644
--- a/docs/en/Tutorials/Part-1.md
+++ b/docs/en/Tutorials/Part-1.md
@@ -34,10 +34,7 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
-> If you encounter the "filename too long" or "unzip" error on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
-
-> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
-> `git config --system core.longpaths true`
+> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md).
{{if UI == "MVC" && DB == "EF"}}
@@ -66,18 +63,17 @@ The main entity of the application is the `Book`. Create a `Books` folder (names
using System;
using Volo.Abp.Domain.Entities.Auditing;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public class Book : AuditedAggregateRoot
{
- public class Book : AuditedAggregateRoot
- {
- public string Name { get; set; }
+ public string Name { get; set; }
- public BookType Type { get; set; }
+ public BookType Type { get; set; }
- public DateTime PublishDate { get; set; }
+ public DateTime PublishDate { get; set; }
- public float Price { get; set; }
- }
+ public float Price { get; set; }
}
````
@@ -92,20 +88,19 @@ namespace Acme.BookStore.Books
The `Book` entity uses the `BookType` enum. Create a `Books` folder (namespace) in the `Acme.BookStore.Domain.Shared` project and add a `BookType` inside it:
````csharp
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public enum BookType
{
- public enum BookType
- {
- Undefined,
- Adventure,
- Biography,
- Dystopia,
- Fantastic,
- Horror,
- Science,
- ScienceFiction,
- Poetry
- }
+ Undefined,
+ Adventure,
+ Biography,
+ Dystopia,
+ Fantastic,
+ Horror,
+ Science,
+ ScienceFiction,
+ Poetry
}
````
@@ -153,34 +148,33 @@ Navigate to the `OnModelCreating` method in the `BookStoreDbContext` class and a
using Acme.BookStore.Books;
...
-namespace Acme.BookStore.EntityFrameworkCore
+namespace Acme.BookStore.EntityFrameworkCore;
+
+public class BookStoreDbContext :
+ AbpDbContext,
+ IIdentityDbContext,
+ ITenantManagementDbContext
{
- public class BookStoreDbContext :
- AbpDbContext,
- IIdentityDbContext,
- ITenantManagementDbContext
- {
- ...
+ ...
- protected override void OnModelCreating(ModelBuilder builder)
- {
- base.OnModelCreating(builder);
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
- /* Include modules to your migration db context */
+ /* Include modules to your migration db context */
- builder.ConfigurePermissionManagement();
- ...
+ builder.ConfigurePermissionManagement();
+ ...
- /* Configure your own tables/entities inside here */
+ /* Configure your own tables/entities inside here */
- builder.Entity(b =>
- {
- b.ToTable(BookStoreConsts.DbTablePrefix + "Books",
- BookStoreConsts.DbSchema);
- b.ConfigureByConvention(); //auto configure for the base class props
- b.Property(x => x.Name).IsRequired().HasMaxLength(128);
- });
- }
+ builder.Entity(b =>
+ {
+ b.ToTable(BookStoreConsts.DbTablePrefix + "Books",
+ BookStoreConsts.DbSchema);
+ b.ConfigureByConvention(); //auto configure for the base class props
+ b.Property(x => x.Name).IsRequired().HasMaxLength(128);
+ });
}
}
````
@@ -202,7 +196,7 @@ This will add a new migration class to the project:

-> If you are using Visual Studio, you may want to use the `Add-Migration Created_Book_Entity -c BookStoreDbContext` and `Update-Database -Context BookStoreDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC.
+> If you are using Visual Studio, you may want to use the `Add-Migration Created_Book_Entity` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that `Acme.BookStore.EntityFrameworkCore` is the startup project in Visual Studio and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC.
{{end}}
@@ -220,44 +214,43 @@ using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
-namespace Acme.BookStore
+namespace Acme.BookStore;
+
+public class BookStoreDataSeederContributor
+ : IDataSeedContributor, ITransientDependency
{
- public class BookStoreDataSeederContributor
- : IDataSeedContributor, ITransientDependency
- {
- private readonly IRepository _bookRepository;
+ private readonly IRepository _bookRepository;
- public BookStoreDataSeederContributor(IRepository bookRepository)
- {
- _bookRepository = bookRepository;
- }
+ public BookStoreDataSeederContributor(IRepository bookRepository)
+ {
+ _bookRepository = bookRepository;
+ }
- public async Task SeedAsync(DataSeedContext context)
+ public async Task SeedAsync(DataSeedContext context)
+ {
+ if (await _bookRepository.GetCountAsync() <= 0)
{
- if (await _bookRepository.GetCountAsync() <= 0)
- {
- await _bookRepository.InsertAsync(
- new Book
- {
- Name = "1984",
- Type = BookType.Dystopia,
- PublishDate = new DateTime(1949, 6, 8),
- Price = 19.84f
- },
- autoSave: true
- );
-
- await _bookRepository.InsertAsync(
- new Book
- {
- Name = "The Hitchhiker's Guide to the Galaxy",
- Type = BookType.ScienceFiction,
- PublishDate = new DateTime(1995, 9, 27),
- Price = 42.0f
- },
- autoSave: true
- );
- }
+ await _bookRepository.InsertAsync(
+ new Book
+ {
+ Name = "1984",
+ Type = BookType.Dystopia,
+ PublishDate = new DateTime(1949, 6, 8),
+ Price = 19.84f
+ },
+ autoSave: true
+ );
+
+ await _bookRepository.InsertAsync(
+ new Book
+ {
+ Name = "The Hitchhiker's Guide to the Galaxy",
+ Type = BookType.ScienceFiction,
+ PublishDate = new DateTime(1995, 9, 27),
+ Price = 42.0f
+ },
+ autoSave: true
+ );
}
}
}
@@ -290,18 +283,17 @@ In this section, you will create an application service to get, create, update a
using System;
using Volo.Abp.Application.Dtos;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public class BookDto : AuditedEntityDto
{
- public class BookDto : AuditedEntityDto
- {
- public string Name { get; set; }
+ public string Name { get; set; }
- public BookType Type { get; set; }
+ public BookType Type { get; set; }
- public DateTime PublishDate { get; set; }
+ public DateTime PublishDate { get; set; }
- public float Price { get; set; }
- }
+ public float Price { get; set; }
}
````
@@ -315,14 +307,13 @@ It will be needed to map the `Book` entities to the `BookDto` objects while retu
using Acme.BookStore.Books;
using AutoMapper;
-namespace Acme.BookStore
+namespace Acme.BookStore;
+
+public class BookStoreApplicationAutoMapperProfile : Profile
{
- public class BookStoreApplicationAutoMapperProfile : Profile
+ public BookStoreApplicationAutoMapperProfile()
{
- public BookStoreApplicationAutoMapperProfile()
- {
- CreateMap();
- }
+ CreateMap();
}
}
````
@@ -337,24 +328,23 @@ Create a `CreateUpdateBookDto` class in the `Books` folder (namespace) of the `A
using System;
using System.ComponentModel.DataAnnotations;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public class CreateUpdateBookDto
{
- public class CreateUpdateBookDto
- {
- [Required]
- [StringLength(128)]
- public string Name { get; set; }
+ [Required]
+ [StringLength(128)]
+ public string Name { get; set; }
- [Required]
- public BookType Type { get; set; } = BookType.Undefined;
+ [Required]
+ public BookType Type { get; set; } = BookType.Undefined;
- [Required]
- [DataType(DataType.Date)]
- public DateTime PublishDate { get; set; } = DateTime.Now;
+ [Required]
+ [DataType(DataType.Date)]
+ public DateTime PublishDate { get; set; } = DateTime.Now;
- [Required]
- public float Price { get; set; }
- }
+ [Required]
+ public float Price { get; set; }
}
````
@@ -367,15 +357,14 @@ As done to the `BookDto` above, we should define the mapping from the `CreateUpd
using Acme.BookStore.Books;
using AutoMapper;
-namespace Acme.BookStore
+namespace Acme.BookStore;
+
+public class BookStoreApplicationAutoMapperProfile : Profile
{
- public class BookStoreApplicationAutoMapperProfile : Profile
+ public BookStoreApplicationAutoMapperProfile()
{
- public BookStoreApplicationAutoMapperProfile()
- {
- CreateMap();
- CreateMap();
- }
+ CreateMap();
+ CreateMap();
}
}
````
@@ -389,17 +378,16 @@ using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public interface IBookAppService :
+ ICrudAppService< //Defines CRUD methods
+ BookDto, //Used to show books
+ Guid, //Primary key of the book entity
+ PagedAndSortedResultRequestDto, //Used for paging/sorting
+ CreateUpdateBookDto> //Used to create/update a book
{
- public interface IBookAppService :
- ICrudAppService< //Defines CRUD methods
- BookDto, //Used to show books
- Guid, //Primary key of the book entity
- PagedAndSortedResultRequestDto, //Used for paging/sorting
- CreateUpdateBookDto> //Used to create/update a book
- {
- }
}
````
@@ -417,22 +405,21 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public class BookAppService :
+ CrudAppService<
+ Book, //The Book entity
+ BookDto, //Used to show books
+ Guid, //Primary key of the book entity
+ PagedAndSortedResultRequestDto, //Used for paging/sorting
+ CreateUpdateBookDto>, //Used to create/update a book
+ IBookAppService //implement the IBookAppService
{
- public class BookAppService :
- CrudAppService<
- Book, //The Book entity
- BookDto, //Used to show books
- Guid, //Primary key of the book entity
- PagedAndSortedResultRequestDto, //Used for paging/sorting
- CreateUpdateBookDto>, //Used to create/update a book
- IBookAppService //implement the IBookAppService
+ public BookAppService(IRepository repository)
+ : base(repository)
{
- public BookAppService(IRepository repository)
- : base(repository)
- {
- }
}
}
````
diff --git a/docs/en/Tutorials/Part-10.md b/docs/en/Tutorials/Part-10.md
index 6418db7e71..6178ebbb8e 100644
--- a/docs/en/Tutorials/Part-10.md
+++ b/docs/en/Tutorials/Part-10.md
@@ -34,10 +34,7 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
-> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
-
-> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
-> `git config --system core.longpaths true`
+> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md).
## Introduction
@@ -55,7 +52,7 @@ public Guid AuthorId { get; set; }
{{if DB=="EF"}}
-> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity from the `Book` class (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their authors (like we will done below) which makes your application code simpler.
+> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity from the `Book` class (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their authors (like we will be doing below) which makes your application code simpler.
{{end}}
@@ -105,6 +102,7 @@ This should create a new migration class with the following code in its `Up` met
migrationBuilder.AddColumn(
name: "AuthorId",
table: "AppBooks",
+ type: "uniqueidentifier",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
@@ -143,72 +141,71 @@ using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
-namespace Acme.BookStore
+namespace Acme.BookStore;
+
+public class BookStoreDataSeederContributor
+ : IDataSeedContributor, ITransientDependency
{
- public class BookStoreDataSeederContributor
- : IDataSeedContributor, ITransientDependency
+ private readonly IRepository _bookRepository;
+ private readonly IAuthorRepository _authorRepository;
+ private readonly AuthorManager _authorManager;
+
+ public BookStoreDataSeederContributor(
+ IRepository bookRepository,
+ IAuthorRepository authorRepository,
+ AuthorManager authorManager)
+ {
+ _bookRepository = bookRepository;
+ _authorRepository = authorRepository;
+ _authorManager = authorManager;
+ }
+
+ public async Task SeedAsync(DataSeedContext context)
{
- private readonly IRepository _bookRepository;
- private readonly IAuthorRepository _authorRepository;
- private readonly AuthorManager _authorManager;
-
- public BookStoreDataSeederContributor(
- IRepository bookRepository,
- IAuthorRepository authorRepository,
- AuthorManager authorManager)
+ if (await _bookRepository.GetCountAsync() > 0)
{
- _bookRepository = bookRepository;
- _authorRepository = authorRepository;
- _authorManager = authorManager;
+ return;
}
- public async Task SeedAsync(DataSeedContext context)
- {
- if (await _bookRepository.GetCountAsync() > 0)
+ var orwell = await _authorRepository.InsertAsync(
+ await _authorManager.CreateAsync(
+ "George Orwell",
+ new DateTime(1903, 06, 25),
+ "Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)."
+ )
+ );
+
+ var douglas = await _authorRepository.InsertAsync(
+ await _authorManager.CreateAsync(
+ "Douglas Adams",
+ new DateTime(1952, 03, 11),
+ "Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'."
+ )
+ );
+
+ await _bookRepository.InsertAsync(
+ new Book
{
- return;
- }
-
- var orwell = await _authorRepository.InsertAsync(
- await _authorManager.CreateAsync(
- "George Orwell",
- new DateTime(1903, 06, 25),
- "Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)."
- )
- );
-
- var douglas = await _authorRepository.InsertAsync(
- await _authorManager.CreateAsync(
- "Douglas Adams",
- new DateTime(1952, 03, 11),
- "Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'."
- )
- );
-
- await _bookRepository.InsertAsync(
- new Book
- {
- AuthorId = orwell.Id, // SET THE AUTHOR
- Name = "1984",
- Type = BookType.Dystopia,
- PublishDate = new DateTime(1949, 6, 8),
- Price = 19.84f
- },
- autoSave: true
- );
-
- await _bookRepository.InsertAsync(
- new Book
- {
- AuthorId = douglas.Id, // SET THE AUTHOR
- Name = "The Hitchhiker's Guide to the Galaxy",
- Type = BookType.ScienceFiction,
- PublishDate = new DateTime(1995, 9, 27),
- Price = 42.0f
- },
- autoSave: true
- );
- }
+ AuthorId = orwell.Id, // SET THE AUTHOR
+ Name = "1984",
+ Type = BookType.Dystopia,
+ PublishDate = new DateTime(1949, 6, 8),
+ Price = 19.84f
+ },
+ autoSave: true
+ );
+
+ await _bookRepository.InsertAsync(
+ new Book
+ {
+ AuthorId = douglas.Id, // SET THE AUTHOR
+ Name = "The Hitchhiker's Guide to the Galaxy",
+ Type = BookType.ScienceFiction,
+ PublishDate = new DateTime(1995, 9, 27),
+ Price = 42.0f
+ },
+ autoSave: true
+ );
}
}
````
@@ -250,22 +247,21 @@ The final `BookDto` class should be following:
using System;
using Volo.Abp.Application.Dtos;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public class BookDto : AuditedEntityDto
{
- public class BookDto : AuditedEntityDto
- {
- public Guid AuthorId { get; set; }
+ public Guid AuthorId { get; set; }
- public string AuthorName { get; set; }
+ public string AuthorName { get; set; }
- public string Name { get; set; }
+ public string Name { get; set; }
- public BookType Type { get; set; }
+ public BookType Type { get; set; }
- public DateTime PublishDate { get; set; }
+ public DateTime PublishDate { get; set; }
- public float Price { get; set; }
- }
+ public float Price { get; set; }
}
```
@@ -285,12 +281,11 @@ Create a new class, `AuthorLookupDto`, inside the `Books` folder of the `Acme.Bo
using System;
using Volo.Abp.Application.Dtos;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public class AuthorLookupDto : EntityDto
{
- public class AuthorLookupDto : EntityDto
- {
- public string Name { get; set; }
- }
+ public string Name { get; set; }
}
````
@@ -306,18 +301,17 @@ using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+public interface IBookAppService :
+ ICrudAppService< //Defines CRUD methods
+ BookDto, //Used to show books
+ Guid, //Primary key of the book entity
+ PagedAndSortedResultRequestDto, //Used for paging/sorting
+ CreateUpdateBookDto> //Used to create/update a book
{
- public interface IBookAppService :
- ICrudAppService< //Defines CRUD methods
- BookDto, //Used to show books
- Guid, //Primary key of the book entity
- PagedAndSortedResultRequestDto, //Used for paging/sorting
- CreateUpdateBookDto> //Used to create/update a book
- {
- // ADD the NEW METHOD
- Task> GetAuthorLookupAsync();
- }
+ // ADD the NEW METHOD
+ Task> GetAuthorLookupAsync();
}
````
@@ -343,119 +337,118 @@ using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+[Authorize(BookStorePermissions.Books.Default)]
+public class BookAppService :
+ CrudAppService<
+ Book, //The Book entity
+ BookDto, //Used to show books
+ Guid, //Primary key of the book entity
+ PagedAndSortedResultRequestDto, //Used for paging/sorting
+ CreateUpdateBookDto>, //Used to create/update a book
+ IBookAppService //implement the IBookAppService
{
- [Authorize(BookStorePermissions.Books.Default)]
- public class BookAppService :
- CrudAppService<
- Book, //The Book entity
- BookDto, //Used to show books
- Guid, //Primary key of the book entity
- PagedAndSortedResultRequestDto, //Used for paging/sorting
- CreateUpdateBookDto>, //Used to create/update a book
- IBookAppService //implement the IBookAppService
+ private readonly IAuthorRepository _authorRepository;
+
+ public BookAppService(
+ IRepository repository,
+ IAuthorRepository authorRepository)
+ : base(repository)
{
- private readonly IAuthorRepository _authorRepository;
+ _authorRepository = authorRepository;
+ GetPolicyName = BookStorePermissions.Books.Default;
+ GetListPolicyName = BookStorePermissions.Books.Default;
+ CreatePolicyName = BookStorePermissions.Books.Create;
+ UpdatePolicyName = BookStorePermissions.Books.Edit;
+ DeletePolicyName = BookStorePermissions.Books.Create;
+ }
- public BookAppService(
- IRepository repository,
- IAuthorRepository authorRepository)
- : base(repository)
+ public override async Task GetAsync(Guid id)
+ {
+ //Get the IQueryable from the repository
+ var queryable = await Repository.GetQueryableAsync();
+
+ //Prepare a query to join books and authors
+ var query = from book in queryable
+ join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
+ where book.Id == id
+ select new { book, author };
+
+ //Execute the query and get the book with author
+ var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query);
+ if (queryResult == null)
{
- _authorRepository = authorRepository;
- GetPolicyName = BookStorePermissions.Books.Default;
- GetListPolicyName = BookStorePermissions.Books.Default;
- CreatePolicyName = BookStorePermissions.Books.Create;
- UpdatePolicyName = BookStorePermissions.Books.Edit;
- DeletePolicyName = BookStorePermissions.Books.Create;
+ throw new EntityNotFoundException(typeof(Book), id);
}
- public override async Task GetAsync(Guid id)
- {
- //Get the IQueryable from the repository
- var queryable = await Repository.GetQueryableAsync();
-
- //Prepare a query to join books and authors
- var query = from book in queryable
- join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
- where book.Id == id
- select new { book, author };
-
- //Execute the query and get the book with author
- var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query);
- if (queryResult == null)
- {
- throw new EntityNotFoundException(typeof(Book), id);
- }
+ var bookDto = ObjectMapper.Map(queryResult.book);
+ bookDto.AuthorName = queryResult.author.Name;
+ return bookDto;
+ }
- var bookDto = ObjectMapper.Map(queryResult.book);
- bookDto.AuthorName = queryResult.author.Name;
- return bookDto;
- }
+ public override async Task> GetListAsync(PagedAndSortedResultRequestDto input)
+ {
+ //Get the IQueryable from the repository
+ var queryable = await Repository.GetQueryableAsync();
- public override async Task> GetListAsync(PagedAndSortedResultRequestDto input)
- {
- //Get the IQueryable from the repository
- var queryable = await Repository.GetQueryableAsync();
+ //Prepare a query to join books and authors
+ var query = from book in queryable
+ join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
+ select new {book, author};
- //Prepare a query to join books and authors
- var query = from book in queryable
- join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
- select new {book, author};
+ //Paging
+ query = query
+ .OrderBy(NormalizeSorting(input.Sorting))
+ .Skip(input.SkipCount)
+ .Take(input.MaxResultCount);
- //Paging
- query = query
- .OrderBy(NormalizeSorting(input.Sorting))
- .Skip(input.SkipCount)
- .Take(input.MaxResultCount);
+ //Execute the query and get a list
+ var queryResult = await AsyncExecuter.ToListAsync(query);
- //Execute the query and get a list
- var queryResult = await AsyncExecuter.ToListAsync(query);
+ //Convert the query result to a list of BookDto objects
+ var bookDtos = queryResult.Select(x =>
+ {
+ var bookDto = ObjectMapper.Map(x.book);
+ bookDto.AuthorName = x.author.Name;
+ return bookDto;
+ }).ToList();
- //Convert the query result to a list of BookDto objects
- var bookDtos = queryResult.Select(x =>
- {
- var bookDto = ObjectMapper.Map(x.book);
- bookDto.AuthorName = x.author.Name;
- return bookDto;
- }).ToList();
+ //Get the total count with another query
+ var totalCount = await Repository.GetCountAsync();
- //Get the total count with another query
- var totalCount = await Repository.GetCountAsync();
+ return new PagedResultDto(
+ totalCount,
+ bookDtos
+ );
+ }
- return new PagedResultDto(
- totalCount,
- bookDtos
- );
- }
+ public async Task> GetAuthorLookupAsync()
+ {
+ var authors = await _authorRepository.GetListAsync();
- public async Task> GetAuthorLookupAsync()
- {
- var authors = await _authorRepository.GetListAsync();
+ return new ListResultDto(
+ ObjectMapper.Map, List>(authors)
+ );
+ }
- return new ListResultDto(
- ObjectMapper.Map, List>(authors)
- );
+ private static string NormalizeSorting(string sorting)
+ {
+ if (sorting.IsNullOrEmpty())
+ {
+ return $"book.{nameof(Book.Name)}";
}
- private static string NormalizeSorting(string sorting)
+ if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase))
{
- if (sorting.IsNullOrEmpty())
- {
- return $"book.{nameof(Book.Name)}";
- }
-
- if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase))
- {
- return sorting.Replace(
- "authorName",
- "author.Name",
- StringComparison.OrdinalIgnoreCase
- );
- }
-
- return $"book.{sorting}";
+ return sorting.Replace(
+ "authorName",
+ "author.Name",
+ StringComparison.OrdinalIgnoreCase
+ );
}
+
+ return $"book.{sorting}";
}
}
```
@@ -487,108 +480,107 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
-namespace Acme.BookStore.Books
+namespace Acme.BookStore.Books;
+
+[Authorize(BookStorePermissions.Books.Default)]
+public class BookAppService :
+ CrudAppService<
+ Book, //The Book entity
+ BookDto, //Used to show books
+ Guid, //Primary key of the book entity
+ PagedAndSortedResultRequestDto, //Used for paging/sorting
+ CreateUpdateBookDto>, //Used to create/update a book
+ IBookAppService //implement the IBookAppService
{
- [Authorize(BookStorePermissions.Books.Default)]
- public class BookAppService :
- CrudAppService<
- Book, //The Book entity
- BookDto, //Used to show books
- Guid, //Primary key of the book entity
- PagedAndSortedResultRequestDto, //Used for paging/sorting
- CreateUpdateBookDto>, //Used to create/update a book
- IBookAppService //implement the IBookAppService
- {
- private readonly IAuthorRepository _authorRepository;
+ private readonly IAuthorRepository _authorRepository;
- public BookAppService(
- IRepository repository,
- IAuthorRepository authorRepository)
- : base(repository)
- {
- _authorRepository = authorRepository;
- GetPolicyName = BookStorePermissions.Books.Default;
- GetListPolicyName = BookStorePermissions.Books.Default;
- CreatePolicyName = BookStorePermissions.Books.Create;
- UpdatePolicyName = BookStorePermissions.Books.Edit;
- DeletePolicyName = BookStorePermissions.Books.Create;
- }
+ public BookAppService(
+ IRepository repository,
+ IAuthorRepository authorRepository)
+ : base(repository)
+ {
+ _authorRepository = authorRepository;
+ GetPolicyName = BookStorePermissions.Books.Default;
+ GetListPolicyName = BookStorePermissions.Books.Default;
+ CreatePolicyName = BookStorePermissions.Books.Create;
+ UpdatePolicyName = BookStorePermissions.Books.Edit;
+ DeletePolicyName = BookStorePermissions.Books.Create;
+ }
- public async override Task GetAsync(Guid id)
- {
- var book = await Repository.GetAsync(id);
- var bookDto = ObjectMapper.Map(book);
+ public async override Task GetAsync(Guid id)
+ {
+ var book = await Repository.GetAsync(id);
+ var bookDto = ObjectMapper.Map(book);
- var author = await _authorRepository.GetAsync(book.AuthorId);
- bookDto.AuthorName = author.Name;
+ var author = await _authorRepository.GetAsync(book.AuthorId);
+ bookDto.AuthorName = author.Name;
- return bookDto;
- }
+ return bookDto;
+ }
- public async override Task>
- GetListAsync(PagedAndSortedResultRequestDto input)
+ public async override Task>
+ GetListAsync(PagedAndSortedResultRequestDto input)
+ {
+ //Set a default sorting, if not provided
+ if (input.Sorting.IsNullOrWhiteSpace())
{
- //Set a default sorting, if not provided
- if (input.Sorting.IsNullOrWhiteSpace())
- {
- input.Sorting = nameof(Book.Name);
- }
+ input.Sorting = nameof(Book.Name);
+ }
- //Get the IQueryable from the repository
- var queryable = await Repository.GetQueryableAsync();
+ //Get the IQueryable from the repository
+ var queryable = await Repository.GetQueryableAsync();
- //Get the books
- var books = await AsyncExecuter.ToListAsync(
- queryable
- .OrderBy(input.Sorting)
- .Skip(input.SkipCount)
- .Take(input.MaxResultCount)
- );
+ //Get the books
+ var books = await AsyncExecuter.ToListAsync(
+ queryable
+ .OrderBy(input.Sorting)
+ .Skip(input.SkipCount)
+ .Take(input.MaxResultCount)
+ );
- //Convert to DTOs
- var bookDtos = ObjectMapper.Map, List>(books);
+ //Convert to DTOs
+ var bookDtos = ObjectMapper.Map, List>(books);
- //Get a lookup dictionary for the related authors
- var authorDictionary = await GetAuthorDictionaryAsync(books);
+ //Get a lookup dictionary for the related authors
+ var authorDictionary = await GetAuthorDictionaryAsync(books);
- //Set AuthorName for the DTOs
- bookDtos.ForEach(bookDto => bookDto.AuthorName =
- authorDictionary[bookDto.AuthorId].Name);
+ //Set AuthorName for the DTOs
+ bookDtos.ForEach(bookDto => bookDto.AuthorName =
+ authorDictionary[bookDto.AuthorId].Name);
- //Get the total count with another query (required for the paging)
- var totalCount = await Repository.GetCountAsync();
+ //Get the total count with another query (required for the paging)
+ var totalCount = await Repository.GetCountAsync();
- return new PagedResultDto(
- totalCount,
- bookDtos
- );
- }
+ return new PagedResultDto(
+ totalCount,
+ bookDtos
+ );
+ }
- public async Task> GetAuthorLookupAsync()
- {
- var authors = await _authorRepository.GetListAsync();
+ public async Task> GetAuthorLookupAsync()
+ {
+ var authors = await _authorRepository.GetListAsync();
- return new ListResultDto(
- ObjectMapper.Map, List>(authors)
- );
- }
+ return new ListResultDto(
+ ObjectMapper.Map, List>(authors)
+ );
+ }
- private async Task>
- GetAuthorDictionaryAsync(List books)
- {
- var authorIds = books
- .Select(b => b.AuthorId)
- .Distinct()
- .ToArray();
+ private async Task>
+ GetAuthorDictionaryAsync(List books)
+ {
+ var authorIds = books
+ .Select(b => b.AuthorId)
+ .Distinct()
+ .ToArray();
- var queryable = await _authorRepository.GetQueryableAsync();
+ var queryable = await _authorRepository.GetQueryableAsync();
- var authors = await AsyncExecuter.ToListAsync(
- queryable.Where(a => authorIds.Contains(a.Id))
- );
+ var authors = await AsyncExecuter.ToListAsync(
+ queryable.Where(a => authorIds.Contains(a.Id))
+ );
- return authors.ToDictionary(x => x.Id, x => x);
- }
+ return authors.ToDictionary(x => x.Id, x => x);
}
}
```
@@ -625,76 +617,76 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
-namespace Acme.BookStore.Books
-{ {{if DB=="Mongo"}}
- [Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
- public class BookAppService_Tests : BookStoreApplicationTestBase
+namespace Acme.BookStore.Books;
+
+ {{if DB=="Mongo"}}
+[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
+public class BookAppService_Tests : BookStoreApplicationTestBase
+{
+ private readonly IBookAppService _bookAppService;
+ private readonly IAuthorAppService _authorAppService;
+
+ public BookAppService_Tests()
{
- private readonly IBookAppService _bookAppService;
- private readonly IAuthorAppService _authorAppService;
+ _bookAppService = GetRequiredService();
+ _authorAppService = GetRequiredService();
+ }
- public BookAppService_Tests()
- {
- _bookAppService = GetRequiredService();
- _authorAppService = GetRequiredService();
- }
+ [Fact]
+ public async Task Should_Get_List_Of_Books()
+ {
+ //Act
+ var result = await _bookAppService.GetListAsync(
+ new PagedAndSortedResultRequestDto()
+ );
+
+ //Assert
+ result.TotalCount.ShouldBeGreaterThan(0);
+ result.Items.ShouldContain(b => b.Name == "1984" &&
+ b.AuthorName == "George Orwell");
+ }
- [Fact]
- public async Task Should_Get_List_Of_Books()
- {
- //Act
- var result = await _bookAppService.GetListAsync(
- new PagedAndSortedResultRequestDto()
- );
+ [Fact]
+ public async Task Should_Create_A_Valid_Book()
+ {
+ var authors = await _authorAppService.GetListAsync(new GetAuthorListDto());
+ var firstAuthor = authors.Items.First();
- //Assert
- result.TotalCount.ShouldBeGreaterThan(0);
- result.Items.ShouldContain(b => b.Name == "1984" &&
- b.AuthorName == "George Orwell");
- }
+ //Act
+ var result = await _bookAppService.CreateAsync(
+ new CreateUpdateBookDto
+ {
+ AuthorId = firstAuthor.Id,
+ Name = "New test book 42",
+ Price = 10,
+ PublishDate = System.DateTime.Now,
+ Type = BookType.ScienceFiction
+ }
+ );
- [Fact]
- public async Task Should_Create_A_Valid_Book()
- {
- var authors = await _authorAppService.GetListAsync(new GetAuthorListDto());
- var firstAuthor = authors.Items.First();
+ //Assert
+ result.Id.ShouldNotBe(Guid.Empty);
+ result.Name.ShouldBe("New test book 42");
+ }
- //Act
- var result = await _bookAppService.CreateAsync(
+ [Fact]
+ public async Task Should_Not_Create_A_Book_Without_Name()
+ {
+ var exception = await Assert.ThrowsAsync(async () =>
+ {
+ await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
- AuthorId = firstAuthor.Id,
- Name = "New test book 42",
+ Name = "",
Price = 10,
- PublishDate = System.DateTime.Now,
+ PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
+ });
- //Assert
- result.Id.ShouldNotBe(Guid.Empty);
- result.Name.ShouldBe("New test book 42");
- }
-
- [Fact]
- public async Task Should_Not_Create_A_Book_Without_Name()
- {
- var exception = await Assert.ThrowsAsync(async () =>
- {
- await _bookAppService.CreateAsync(
- new CreateUpdateBookDto
- {
- Name = "",
- Price = 10,
- PublishDate = DateTime.Now,
- Type = BookType.ScienceFiction
- }
- );
- });
-
- exception.ValidationErrors
- .ShouldContain(err => err.MemberNames.Any(m => m == "Name"));
- }
+ exception.ValidationErrors
+ .ShouldContain(err => err.MemberNames.Any(m => m == "Name"));
}
}
```
@@ -708,7 +700,7 @@ namespace Acme.BookStore.Books
### The Book List
-Book list page change is trivial. Open the `Pages/Books/Index.js` in the `Acme.BookStore.Web` project and add the following column definition between the `name` and `type` columns:
+Book list page change is trivial. Open the `Pages/Books/Index.js` in the `Acme.BookStore.Web` project and add an `authorName` column between the `name` and `type` columns:
````js
...
@@ -727,7 +719,7 @@ Book list page change is trivial. Open the `Pages/Books/Index.js` in the `Acme.B
title: l('Type'),
data: "type",
render: function (data) {
- return l('Enum:BookType:' + data);
+ return l('Enum:BookType.' + data);
}
},
...
@@ -735,7 +727,7 @@ Book list page change is trivial. Open the `Pages/Books/Index.js` in the `Acme.B
When you run the application, you can see the *Author* column on the table:
-
+
### Create Modal
@@ -753,61 +745,60 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
-namespace Acme.BookStore.Web.Pages.Books
+namespace Acme.BookStore.Web.Pages.Books;
+
+public class CreateModalModel : BookStorePageModel
{
- public class CreateModalModel : BookStorePageModel
- {
- [BindProperty]
- public CreateBookViewModel Book { get; set; }
+ [BindProperty]
+ public CreateBookViewModel Book { get; set; }
- public List Authors { get; set; }
+ public List Authors { get; set; }
- private readonly IBookAppService _bookAppService;
+ private readonly IBookAppService _bookAppService;
- public CreateModalModel(
- IBookAppService bookAppService)
- {
- _bookAppService = bookAppService;
- }
+ public CreateModalModel(
+ IBookAppService bookAppService)
+ {
+ _bookAppService = bookAppService;
+ }
- public async Task OnGetAsync()
- {
- Book = new CreateBookViewModel();
+ public async Task OnGetAsync()
+ {
+ Book = new CreateBookViewModel();
- var authorLookup = await _bookAppService.GetAuthorLookupAsync();
- Authors = authorLookup.Items
- .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
- .ToList();
- }
+ var authorLookup = await _bookAppService.GetAuthorLookupAsync();
+ Authors = authorLookup.Items
+ .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
+ .ToList();
+ }
- public async Task OnPostAsync()
- {
- await _bookAppService.CreateAsync(
- ObjectMapper.Map(Book)
- );
- return NoContent();
- }
+ public async Task OnPostAsync()
+ {
+ await _bookAppService.CreateAsync(
+ ObjectMapper.Map(Book)
+ );
+ return NoContent();
+ }
- public class CreateBookViewModel
- {
- [SelectItems(nameof(Authors))]
- [DisplayName("Author")]
- public Guid AuthorId { get; set; }
+ public class CreateBookViewModel
+ {
+ [SelectItems(nameof(Authors))]
+ [DisplayName("Author")]
+ public Guid AuthorId { get; set; }
- [Required]
- [StringLength(128)]
- public string Name { get; set; }
+ [Required]
+ [StringLength(128)]
+ public string Name { get; set; }
- [Required]
- public BookType Type { get; set; } = BookType.Undefined;
+ [Required]
+ public BookType Type { get; set; } = BookType.Undefined;
- [Required]
- [DataType(DataType.Date)]
- public DateTime PublishDate { get; set; } = DateTime.Now;
+ [Required]
+ [DataType(DataType.Date)]
+ public DateTime PublishDate { get; set; } = DateTime.Now;
- [Required]
- public float Price { get; set; }
- }
+ [Required]
+ public float Price { get; set; }
}
}
```
@@ -832,66 +823,65 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
-namespace Acme.BookStore.Web.Pages.Books
+namespace Acme.BookStore.Web.Pages.Books;
+
+public class EditModalModel : BookStorePageModel
{
- public class EditModalModel : BookStorePageModel
- {
- [BindProperty]
- public EditBookViewModel Book { get; set; }
+ [BindProperty]
+ public EditBookViewModel Book { get; set; }
- public List Authors { get; set; }
+ public List Authors { get; set; }
- private readonly IBookAppService _bookAppService;
+ private readonly IBookAppService _bookAppService;
- public EditModalModel(IBookAppService bookAppService)
- {
- _bookAppService = bookAppService;
- }
+ public EditModalModel(IBookAppService bookAppService)
+ {
+ _bookAppService = bookAppService;
+ }
- public async Task OnGetAsync(Guid id)
- {
- var bookDto = await _bookAppService.GetAsync(id);
- Book = ObjectMapper.Map(bookDto);
+ public async Task OnGetAsync(Guid id)
+ {
+ var bookDto = await _bookAppService.GetAsync(id);
+ Book = ObjectMapper.Map(bookDto);
- var authorLookup = await _bookAppService.GetAuthorLookupAsync();
- Authors = authorLookup.Items
- .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
- .ToList();
- }
+ var authorLookup = await _bookAppService.GetAuthorLookupAsync();
+ Authors = authorLookup.Items
+ .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
+ .ToList();
+ }
- public async Task OnPostAsync()
- {
- await _bookAppService.UpdateAsync(
- Book.Id,
- ObjectMapper.Map(Book)
- );
+ public async Task OnPostAsync()
+ {
+ await _bookAppService.UpdateAsync(
+ Book.Id,
+ ObjectMapper.Map(Book)
+ );
- return NoContent();
- }
+ return NoContent();
+ }
- public class EditBookViewModel
- {
- [HiddenInput]
- public Guid Id { get; set; }
+ public class EditBookViewModel
+ {
+ [HiddenInput]
+ public Guid Id { get; set; }
- [SelectItems(nameof(Authors))]
- [DisplayName("Author")]
- public Guid AuthorId { get; set; }
+ [SelectItems(nameof(Authors))]
+ [DisplayName("Author")]
+ public Guid AuthorId { get; set; }
- [Required]
- [StringLength(128)]
- public string Name { get; set; }
+ [Required]
+ [StringLength(128)]
+ public string Name { get; set; }
- [Required]
- public BookType Type { get; set; } = BookType.Undefined;
+ [Required]
+ public BookType Type { get; set; } = BookType.Undefined;
- [Required]
- [DataType(DataType.Date)]
- public DateTime PublishDate { get; set; } = DateTime.Now;
+ [Required]
+ [DataType(DataType.Date)]
+ public DateTime PublishDate { get; set; } = DateTime.Now;
- [Required]
- public float Price { get; set; }
- }
+ [Required]
+ public float Price { get; set; }
}
}
```
@@ -937,7 +927,7 @@ CreateMap();
You can run the application and try to create a new book or update an existing book. You will see a drop down list on the create/update form to select the author of the book:
-
+
{{else if UI=="NG"}}
@@ -966,13 +956,13 @@ Book list page change is trivial. Open the `/src/app/book/book.component.html` a
When you run the application, you can see the *Author* column on the table:
-
+
### Create/Edit Forms
The next step is to add an Author selection (dropdown) to the create/edit forms. The final UI will look like the one shown below:
-
+
Added the Author dropdown as the first element in the form.
@@ -1114,7 +1104,7 @@ It is very easy to show the *Author Name* in the book list. Open the `/Pages/Boo
When you run the application, you can see the *Author* column on the table:
-
+
### Create Book Modal
@@ -1136,6 +1126,21 @@ protected override async Task OnInitializedAsync()
* It is essential to call the `base.OnInitializedAsync()` since `AbpCrudPageBase` has some initialization code to be executed.
+Override the `OpenCreateModalAsync` method and adding the following code:
+
+````csharp
+protected override async Task OpenCreateModalAsync()
+{
+ if (!authorList.Any())
+ {
+ throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]);
+ }
+
+ await base.OpenCreateModalAsync();
+ NewEntity.AuthorId = authorList.First().Id;
+}
+````
+
The final `@code` block should be the following:
````csharp
@@ -1157,6 +1162,17 @@ The final `@code` block should be the following:
await base.OnInitializedAsync();
authorList = (await AppService.GetAuthorLookupAsync()).Items;
}
+
+ protected override async Task OpenCreateModalAsync()
+ {
+ if (!authorList.Any())
+ {
+ throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]);
+ }
+
+ await base.OpenCreateModalAsync();
+ NewEntity.AuthorId = authorList.First().Id;
+ }
}
````
@@ -1166,7 +1182,6 @@ Finally, add the following `Field` definition into the `ModalBody` of the *Creat
@L["Author"]