@ -0,0 +1,14 @@ |
|||
### Description |
|||
|
|||
Resolves #xxxx (write the related issue number if available) |
|||
|
|||
TODO: Describe what this PR has changed, add screenshot or animated GIF if available, write if it is a breaking change, and how to fix the breaking changes for existing applications if so. |
|||
|
|||
### Checklist |
|||
|
|||
- [ ] I fully tested it as developer / designer and created unit / integration tests |
|||
- [ ] I documented it (or no need to document or I will create a separate documentation issue) |
|||
|
|||
### How to test it? |
|||
|
|||
Please describe how this can be tested by the test engineers if it is not already explicit - or remove this section if no need to description. |
|||
@ -1,31 +0,0 @@ |
|||
name: Documentation Checks |
|||
|
|||
on: |
|||
push: |
|||
branches: |
|||
- dev |
|||
paths: |
|||
# This ensures the check will only be run when something changes in the docs content |
|||
- "docs/en/**/*" |
|||
pull_request: |
|||
branches: |
|||
- dev |
|||
paths: |
|||
- "docs/en/**/*" |
|||
permissions: |
|||
contents: read # to fetch code (actions/checkout) |
|||
jobs: |
|||
spellcheck: |
|||
name: "Docs: Spellcheck (En)" |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- uses: actions/checkout@v2 |
|||
name: Check out the code |
|||
- uses: actions/setup-node@v1 |
|||
name: Setup node |
|||
with: |
|||
node-version: "16" |
|||
- run: npm install -g cspell |
|||
name: Install cSpell |
|||
- run: cspell --config ./cSpell.json "docs/en/**/*.md" --no-progress # Update for path to the markdown files |
|||
name: Run cSpell |
|||
@ -1,151 +0,0 @@ |
|||
{ |
|||
"version": "0.2", |
|||
"language": "en", |
|||
"words": [ |
|||
"ABP's", |
|||
"abpframework", |
|||
"Antiforgery", |
|||
"appsettings", |
|||
"aspnet", |
|||
"aspnetcore", |
|||
"Autofac", |
|||
"automagically", |
|||
"Blazor", |
|||
"CQRS", |
|||
"crossfade", |
|||
"Dapr", |
|||
"Datagrid's", |
|||
"Datatable", |
|||
"datepicker", |
|||
"dismissable", |
|||
"dockerized", |
|||
"entrypoints", |
|||
"findability", |
|||
"hoverable", |
|||
"Iddict", |
|||
"IntelliCode", |
|||
"Keysize", |
|||
"Linq", |
|||
"Microservices", |
|||
"middlewares", |
|||
"Minifier", |
|||
"multitenancy", |
|||
"multitenant", |
|||
"Navs", |
|||
"Newtonsoft", |
|||
"Npgsql", |
|||
"oidc", |
|||
"overridable", |
|||
"Parameterless", |
|||
"Passwordless", |
|||
"PKCE", |
|||
"preconfigured", |
|||
"proxying", |
|||
"redirections", |
|||
"scrollbars", |
|||
"signin", |
|||
"Templating", |
|||
"textboxes", |
|||
"toolset", |
|||
"unsubscription", |
|||
"Xunit" |
|||
], |
|||
"ignoreWords": [ |
|||
"Aliyun", |
|||
"Allibone", |
|||
"Blazorise", |
|||
"Boutwell", |
|||
"Cmskit", |
|||
"connectionstrings", |
|||
"Devart", |
|||
"Formik", |
|||
"Halil", |
|||
"Hanselman", |
|||
"hikalkan", |
|||
"Ibrahim", |
|||
"İbrahim", |
|||
"Kalkan", |
|||
"Kirti", |
|||
"Kommunity", |
|||
"Kulkarni", |
|||
"Luxon", |
|||
"malihu", |
|||
"Malik", |
|||
"Masis", |
|||
"Minio", |
|||
"NGXS", |
|||
"NSWAG", |
|||
"Scriban", |
|||
"Serilog", |
|||
"Shoudly", |
|||
"Shouldly", |
|||
"Sweetalert", |
|||
"Syncfusion", |
|||
"Telerik", |
|||
"Timeago", |
|||
"Toastr", |
|||
"Volo", |
|||
"Volosoft", |
|||
"Xeevis" |
|||
], |
|||
"patterns": [ |
|||
{ |
|||
"name": "Markdown links", |
|||
"pattern": "\\((.*)\\)", |
|||
"description": "" |
|||
}, |
|||
{ |
|||
"name": "Markdown code blocks", |
|||
"pattern": "/^(\\s*`{3,}).*[\\s\\S]*?^\\1/gmx", |
|||
"description": "Taken from the cSpell example at https://cspell.org/configuration/patterns/#verbose-regular-expressions" |
|||
}, |
|||
{ |
|||
"name": "Inline code blocks", |
|||
"pattern": "\\`([^\\`\\r\\n]+?)\\`", |
|||
"description": "https://stackoverflow.com/questions/41274241/how-to-capture-inline-markdown-code-but-not-a-markdown-code-fence-with-regex" |
|||
}, |
|||
{ |
|||
"name": "Link contents", |
|||
"pattern": "\\<a(.*)\\>", |
|||
"description": "" |
|||
}, |
|||
{ |
|||
"name": "Snippet references", |
|||
"pattern": "-- snippet:(.*)", |
|||
"description": "" |
|||
}, |
|||
{ |
|||
"name": "Snippet references 2", |
|||
"pattern": "\\<\\[sample:(.*)", |
|||
"description": "another kind of snippet reference" |
|||
}, |
|||
{ |
|||
"name": "Multi-line code blocks", |
|||
"pattern": "/^\\s*```[\\s\\S]*?^\\s*```/gm" |
|||
}, |
|||
{ |
|||
"name": "HTML Tags", |
|||
"pattern": "<[^>]*>", |
|||
"description": "Reference: https://stackoverflow.com/questions/11229831/regular-expression-to-remove-html-tags-from-a-string" |
|||
}, |
|||
{ |
|||
"name": "Markdown Image", |
|||
"pattern": "!\\[(.*)\\]\\((.*)\\)" |
|||
} |
|||
], |
|||
"ignoreRegExpList": [ |
|||
"Markdown links", |
|||
"Markdown code blocks", |
|||
"Inline code blocks", |
|||
"Link contents", |
|||
"Snippet references", |
|||
"Snippet references 2", |
|||
"Multi-line code blocks", |
|||
"HTML Tags", |
|||
"Markdown Image" |
|||
], |
|||
"ignorePaths": [ |
|||
"**/*Release/Post.md", |
|||
"**/*Preview/POST.md" |
|||
] |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
# Application Localization Endpoint |
|||
|
|||
ABP Framework provides a pre-built and standard endpoint that returns all the [localization](../Localization.md) resources and texts defined in the server. |
|||
|
|||
> If you have started with ABP's startup solution templates and using one of the official UI options, then all these are set up for you and you don't need to know these details. However, if you are building a UI application from scratch, you may want to know this endpoint. |
|||
|
|||
## HTTP API |
|||
|
|||
`/api/abp/application-localization` is the main URL of the HTTP API that returns the localization data as a JSON string. I accepts the following query string parameters: |
|||
|
|||
* `cultureName` (required): A culture code to get the localization data, like `en` or `en-US`. |
|||
* `onlyDynamics` (optional, default: `false`): Can be set to `true` to only get the dynamically defined localization resources and texts. If your client-side application shares the same localization resources with the server (like ABP's Blazor and MVC UIs), you can set `onlyDynamics` to `true`. |
|||
|
|||
**Example request:** |
|||
|
|||
```` |
|||
/api/abp/application-localization?cultureName=en |
|||
```` |
|||
|
|||
## Script |
|||
|
|||
For [ASP.NET Core MVC (Razor Pages)](../UI/AspNetCore/Overall.md) applications, the same localization data is also available on the JavaScript side. `/Abp/ApplicationLocalizationScript` is the URL of the script that is auto-generated based on the HTTP API above. |
|||
|
|||
**Example request:** |
|||
|
|||
```` |
|||
/Abp/ApplicationLocalizationScript?cultureName=en |
|||
```` |
|||
|
|||
See the [JavaScript API document](../UI/AspNetCore/JavaScript-API/Index.md) for the ASP.NET Core UI. |
|||
|
|||
Other UI types provide services native to the related platform. For example, see the [Angular UI localization documentation](../UI/Angular/Localization.md) to learn how to use the localization values exposes by this endpoint. |
|||
|
|||
@ -0,0 +1,318 @@ |
|||
## ABP Application Startup |
|||
|
|||
You typically use the [ABP CLI](CLI.md)'s `abp new` command to [get started](Getting-Started.md) with one of the pre-built [startup solution templates](Startup-Templates/Index.md). When you do that, you generally don't need to know the details of how the ABP Framework is integrated with your application or how it is configured and initialized. The startup template also comes with the fundamental ABP packages and [application modules](Modules/Index) are pre-installed and configured for you. |
|||
|
|||
> It is always suggested to [get started with a startup template](Getting-Started.md) and modify it for your requirements. Read this document only if you want to understand the details or if you need to modify how the ABP Framework starts. |
|||
|
|||
While the ABP Framework has a lot of features and integrations, it is built as a lightweight and modular framework. It consists of [hundreds of NuGet and NPM packages](https://abp.io/packages), so you can only use the features you need. If you follow the [Getting Started with an Empty ASP.NET Core MVC / Razor Pages Application](Getting-Started-AspNetCore-Application.md) document, you'll see how easy it is to install the ABP Framework into an empty ASP.NET Core project from scratch. You only need to install a single NuGet package and make a few small changes. |
|||
|
|||
This document is for who wants to better understand how the ABP Framework is initialized and configured on startup. |
|||
|
|||
## Installing to a Console Application |
|||
|
|||
A .NET Console application is the minimalist .NET application. So, it is best to show the installing of the ABP Framework to a console application as a minimalist example. |
|||
|
|||
If you [create a new console application with Visual Studio](https://learn.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio) (for .NET 7.0 or later), you will see the following solution structure (I named the solution as `MyConsoleDemo`): |
|||
|
|||
 |
|||
|
|||
This example uses the [top level statements](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements), so it consists of only a single line of code. |
|||
|
|||
The first step is to install the [Volo.Abp.Core](https://www.nuget.org/packages/Volo.Abp.Core) NuGet package, which is the most core NuGet package of the ABP framework. You can install it using the [Package Manager Console](https://learn.microsoft.com/en-us/nuget/consume-packages/install-use-packages-powershell) in Visual Studio: |
|||
|
|||
````bash |
|||
Install-Package Volo.Abp.Core |
|||
```` |
|||
|
|||
Alternatively, you can use a command-line terminal in the root folder of the project (the folder containing the `MyConsoleDemo.csproj` file, for this example): |
|||
|
|||
````bash |
|||
dotnet add package Volo.Abp.Core |
|||
```` |
|||
|
|||
After adding the NuGet package, we should create a root [module class](Module-Development-Basics.md) for our application. We can create the following class in the project: |
|||
|
|||
````csharp |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace MyConsoleDemo |
|||
{ |
|||
public class MyConsoleDemoModule : AbpModule |
|||
{ |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This is an empty class deriving from the `AbpModule` class. It is the main class that you will control your application's dependencies with, and implement your configuration and startup/shutdown logic. For more information, please check the [Modularity](Module-Development-Basics.md) document. |
|||
|
|||
As the second and the last step, change the `Program.cs` as shown in the following code block: |
|||
|
|||
````csharp |
|||
using MyConsoleDemo; |
|||
using Volo.Abp; |
|||
|
|||
// 1: Create the ABP application container |
|||
using var application = await AbpApplicationFactory.CreateAsync<MyConsoleDemoModule>(); |
|||
|
|||
// 2: Initialize/start the ABP Framework (and all the modules) |
|||
await application.InitializeAsync(); |
|||
|
|||
Console.WriteLine("ABP Framework has been started..."); |
|||
|
|||
// 3: Stop the ABP Framework (and all the modules) |
|||
await application.ShutdownAsync(); |
|||
```` |
|||
|
|||
That's all. Now, ABP Framework is installed, integrated, started and stopped in your application. From now, you can install [ABP packages](https://abp.io/packages) to your application whenever you need them. |
|||
|
|||
## Installing a Framework Package |
|||
|
|||
If you want to send emails from your .NET application, you can use .NET's standard [SmtpClient class](https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient). ABP also provides an `IEmailSender` service that simplifies [sending emails](Emailing.md) and configuring the email settings in a central place. If you want to use it, you should install the [Volo.Abp.Emailing](https://www.nuget.org/packages/Volo.Abp.Emailing) NuGet package to your project: |
|||
|
|||
````bash |
|||
dotnet add package Volo.Abp.Emailing |
|||
```` |
|||
|
|||
Once you add a new ABP package/module, you also need to specify the module dependency from your module class. So, change the `MyConsoleDemoModule` class as shown below: |
|||
|
|||
````csharp |
|||
using Volo.Abp.Emailing; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace MyConsoleDemo |
|||
{ |
|||
[DependsOn(typeof(AbpEmailingModule))] // Added the module dependency |
|||
public class MyConsoleDemoModule : AbpModule |
|||
{ |
|||
} |
|||
} |
|||
```` |
|||
|
|||
I've just added a `[DependsOn]` attribute to declare that I want to use the ABP Emailing Module (`AbpEmailingModule`). Now, I can use the `IEmailSender` service in my `Program.cs`: |
|||
|
|||
````csharp |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using MyConsoleDemo; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Emailing; |
|||
|
|||
using var application = await AbpApplicationFactory.CreateAsync<MyConsoleDemoModule>(); |
|||
await application.InitializeAsync(); |
|||
|
|||
// Sending emails using the IEmailSender service |
|||
var emailsender = application.ServiceProvider.GetRequiredService<IEmailSender>(); |
|||
await emailsender.SendAsync( |
|||
to: "info@acme.com", |
|||
subject: "Hello World", |
|||
body: "My message body..." |
|||
); |
|||
|
|||
await application.ShutdownAsync(); |
|||
```` |
|||
|
|||
> If you run that application, you get a runtime error indicating that the email sending settings haven't been done yet. You can check the [Email Sending document](Emailing.md) to learn how to configure it. |
|||
|
|||
That's all. Install an ABP NuGet package, add the module dependency (using the `[DependsOn]` attribute) and use any service inside the NuGet package. |
|||
|
|||
The [ABP CLI](CLI.md) already has a special command to perform the addition of an ABP NuGet and also adding the `[DependsOn]` attribute to your module class for you with a single command: |
|||
|
|||
````bash |
|||
abp add-package Volo.Abp.Emailing |
|||
```` |
|||
|
|||
We suggest you to use the `abp add-package` command instead of manually doing it. |
|||
|
|||
## AbpApplicationFactory |
|||
|
|||
`AbpApplicationFactory` is the main class that creates an ABP application container. It provides a single static `CreateAsync` (and `Create` if you can't use asynchronous programming) method with multiple overloads. Let's investigate these overloads to understand where you can use them. |
|||
|
|||
The first overload gets a generic module class parameter as we've used before in this document: |
|||
|
|||
````csharp |
|||
AbpApplicationFactory.CreateAsync<MyConsoleDemoModule>(); |
|||
```` |
|||
|
|||
The generic class parameter should be the root module class of your application. All the other modules are resolved as dependencies of that module. |
|||
|
|||
The second overload gets the module class as a `Type` parameter, instead of the generic parameter. So, the previous code block could be re-written as shown below: |
|||
|
|||
````csharp |
|||
AbpApplicationFactory.CreateAsync(typeof(MyConsoleDemoModule)); |
|||
```` |
|||
|
|||
Both overloads work exactly the same. So, you can use the second one if you don't know the module class type on development time and you (somehow) calculate it on runtime. |
|||
|
|||
If you create one of the methods above, ABP creates an internal service collection (`IServiceCollection`) and an internal service provider (`IServiceProvider`) to setup the [dependency injection](Dependency-Injection.md) system internally. Notice that we've used the `application.ServiceProvider` property in the *Installing a Framework Package* section to resolve the `IEmailSender` service from the dependency injection system. |
|||
|
|||
The next overload gets an `IServiceCollection` parameter from you to allow you to setup the dependency injection system yourself, or integrate to another framework (like ASP.NET Core) that also sets up the dependency injection system internally. |
|||
|
|||
We can change the `Program.cs` as shown below to externally manage the dependency injection setup: |
|||
|
|||
````csharp |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using MyConsoleDemo; |
|||
using Volo.Abp; |
|||
|
|||
// 1: Manually created the IServiceCollection |
|||
IServiceCollection services = new ServiceCollection(); |
|||
|
|||
// 2: Pass the IServiceCollection externally to the ABP Framework |
|||
using var application = await AbpApplicationFactory |
|||
.CreateAsync<MyConsoleDemoModule>(services); |
|||
|
|||
// 3: Manually built the IServiceProvider object |
|||
IServiceProvider serviceProvider = services.BuildServiceProvider(); |
|||
|
|||
// 4: Pass the IServiceProvider externally to the ABP Framework |
|||
await application.InitializeAsync(serviceProvider); |
|||
|
|||
Console.WriteLine("ABP Framework has been started..."); |
|||
|
|||
await application.ShutdownAsync(); |
|||
```` |
|||
|
|||
In this example, we've used .NET's standard dependency injection container. The `services.BuildServiceProvider()` call creates the standard container. However, ABP provides an alternative extension method, `BuildServiceProviderFromFactory()`, that properly works even if you are using another dependency injection container: |
|||
|
|||
````csharp |
|||
IServiceProvider serviceProvider = services.BuildServiceProviderFromFactory(); |
|||
```` |
|||
|
|||
> You can check the [Autofac Integration](Autofac-Integration.md) document if you want to learn how you can integrate the [Autofac](https://autofac.org/) dependency injection container with the ABP Framework. |
|||
|
|||
Finally, the `CreateAsync` method has a last overload that takes the module class name as a `Type` parameter and a `IServiceCollection` object. So, we could re-write the last `CreateAsync` method usage as in the following code block: |
|||
|
|||
````csharp |
|||
using var application = await AbpApplicationFactory |
|||
.CreateAsync(typeof(MyConsoleDemoModule), services); |
|||
```` |
|||
|
|||
> All of the `CreateAsync` method overloads have `Create` counterparts. If your application type can not utilize asynchronous programming (that means you can't use the `await` keyword), then you can use the `Create` method instead of the `CreateAsync` method. |
|||
|
|||
### AbpApplicationCreationOptions |
|||
|
|||
All of the `CreateAsync` overloads can get an optional `Action<AbpApplicationCreationOptions>` parameter to configure the options that are used on the application creation. See the following example: |
|||
|
|||
````csharp |
|||
using var application = await AbpApplicationFactory |
|||
.CreateAsync<MyConsoleDemoModule>(options => |
|||
{ |
|||
options.ApplicationName = "MyApp"; |
|||
}); |
|||
```` |
|||
|
|||
We've passed a lambda method to configure the `ApplicationName` option. Here's a list of all standard options: |
|||
|
|||
* `ApplicationName`: A human-readable name for the application. It is a unique value for an application. |
|||
* `Configuration`: Can be used to setup the [application configuration](Configuration.md) when it is not provided by the hosting system. It is not needed for ASP.NET Core and other .NET hosted applications. However, if you've used `AbpApplicationFactory` with an internal service provider, you can use this option to configure how the application configuration is built. |
|||
* `Environment`: Environment name for the application. |
|||
* `PlugInSources`: A list of plugin sources. See the [Plug-In Modules documentation](PlugIn-Modules) to learn how to work with plugins. |
|||
* `Services`: The `IServiceCollection` object that can be used to register service dependencies. You generally don't need that, because you configure your services in your [module class](Module-Development-Basics.md). However, it can be used while writing extension methods for the `AbpApplicationCreationOptions` class. |
|||
|
|||
#### The ApplicationName option |
|||
|
|||
As defined above, the `ApplicationName` option is a human-readable name for the application. It is a unique value for an application. |
|||
|
|||
`ApplicationName` is used by the ABP Framework in several places to distinguish the application. 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 (like a microservice solution) that are 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<OrderingServiceHttpApiHostModule>(options => |
|||
{ |
|||
options.ApplicationName = "Services.Ordering"; |
|||
}); |
|||
```` |
|||
|
|||
#### IApplicationInfoAccessor |
|||
|
|||
If you need to access the `ApplicationName` later in your solution, you can inject the `IApplicationInfoAccessor` service and get the value from its `ApplicationName` property. |
|||
|
|||
`IApplicationInfoAccessor` also provides an `InstanceId` value, that is a random GUID value that is generated when your application starts. You can use that value to distinguish application instances from each other. |
|||
|
|||
## IAbpApplication |
|||
|
|||
`AbpApplicationFactory` returns an `IAbpApplication` object from its `CreateAsync` (or `Create`) method. `IAbpApplication` is the main container for an ABP application. It is also registered to the [dependency injection](Dependency-Injection.md) system, so you can inject `IAbpApplication` in your services to use its properties and methods. |
|||
|
|||
Here's a list of `IAbpApplication` properties you may want to know: |
|||
|
|||
* `StartupModuleType`: Gets the root module of the application that was used while creating the application container (on the `AbpApplicationFactory.CreateAsync` method). |
|||
* `Services`: A list of all service registrations (the `IServiceCollection` object). You can not add new services to this collection after application initialization (you can actually add, but it won't have any effect). |
|||
* `ServiceProvider`: A reference to the root service provider used by the application. This can not be used before initializing the application. If you need to resolve non-singleton services from that `IServiceProvider` object, always create a new service scope and dispose it after usage. Otherwise, your application will have memory leak problems. See the *Releasing/Disposing Services* section of the [dependency injection](Dependency-Injection.md) document for more information about service scopes. |
|||
* `Modules`: A read-only list of all the modules loaded into the current application. Alternatively, you can inject the `IModuleContainer` service if you need to access the module list in your application code. |
|||
|
|||
The `IAbpApplication` interface extends the `IApplicationInfoAccessor` interface, so you can get the `ApplicationName` and `InstanceId` values from it. However, if you only need to access these properties, inject and use the `IApplicationInfoAccessor` service instead. |
|||
|
|||
`IAbpApplication` is disposable. Always dispose of it before exiting your application. |
|||
|
|||
## IAbpHostEnvironment |
|||
|
|||
Sometimes, while creating an application, we need to get the current hosting environment and take actions according to that. In such cases, we can use some services such as [IWebHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.iwebhostenvironment?view=aspnetcore-7.0) or [IWebAssemblyHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.hosting.iwebassemblyhostenvironment) provided by .NET, in the final application. |
|||
|
|||
However, we can not use these services in a class library, which is used by the final application. ABP Framework provides the `IAbpHostEnvironment` service, which allows you to get the current environment name whenever you want. `IAbpHostEnvironment` is used by the ABP Framework in several places to perform specific actions by the environment. For example, ABP Framework reduces the cache duration on the **Development** environment for some services. |
|||
|
|||
`IAbpHostEnvironment` obtains the current environment name by the following order: |
|||
|
|||
1. Gets and sets the environment name if it's specified in the `AbpApplicationCreationOptions`. |
|||
2. Tries to obtain the environment name from the `IWebHostEnvironment` or `IWebAssemblyHostEnvironment` services for ASP.NET Core & Blazor WASM applications if the environment name isn't specified in the `AbpApplicationCreationOptions`. |
|||
3. Sets the environment name as **Production**, if the environment name is not specified or can not be obtained from the services. |
|||
|
|||
You can configure the `AbpApplicationCreationOptions` [options class](Options.md) while creating the ABP application and set an environment name to its `Environment` property. You can find the `AddApplication` or `AddApplicationAsync` call in your solution (typically in the `Program.cs` file), and set the `Environment` option as shown below: |
|||
|
|||
```csharp |
|||
await builder.AddApplicationAsync<OrderingServiceHttpApiHostModule>(options => |
|||
{ |
|||
options.Environment = Environments.Staging; //or directly set as "Staging" |
|||
}); |
|||
``` |
|||
|
|||
Then, whenever you need to get the current environment name or check the environment, you can use the `IAbpHostEnvironment` interface: |
|||
|
|||
```csharp |
|||
public class MyDemoService |
|||
{ |
|||
private readonly IAbpHostEnvironment _abpHostEnvironment; |
|||
|
|||
public MyDemoService(IAbpHostEnvironment abpHostEnvironment) |
|||
{ |
|||
_abpHostEnvironment = abpHostEnvironment; |
|||
} |
|||
|
|||
public void MyMethod() |
|||
{ |
|||
var environmentName = _abpHostEnvironment.EnvironmentName; |
|||
|
|||
if (_abpHostEnvironment.IsDevelopment()) { /* ... */ } |
|||
|
|||
if (_abpHostEnvironment.IsStaging()) { /* ... */ } |
|||
|
|||
if (_abpHostEnvironment.IsProduction()) { /* ... */ } |
|||
|
|||
if (_abpHostEnvironment.IsEnvironment("custom-environment")) { /* ... */ } |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## .NET Generic Host & ASP.NET Core Integrations |
|||
|
|||
`AbpApplicationFactory` can create a standalone ABP application container without any external dependency. However, in most cases, you will want to integrate it with [.NET's generic host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host) or ASP.NET Core. For such usages, ABP provides built-in extension methods to easily create an ABP application container that is well-integrated to these systems. |
|||
|
|||
The [Getting Started with an Empty ASP.NET Core MVC / Razor Pages Application](Getting-Started-AspNetCore-Application.md) document clearly explains how you can create an ABP application container in an ASP.NET Core application. |
|||
|
|||
You can also [create a console application](Startup-Templates/Console) to see how it is integrated with .NET Generic Host. |
|||
|
|||
> Most of the times, you will directly create ABP applications using the ABP CLI's `new` command. So, you don't need to care about these integration details. |
|||
|
|||
## See Also |
|||
|
|||
* [Dependency injection](Dependency-Injection.md) |
|||
* [Modularity](Module-Development-Basics.md) |
|||
@ -0,0 +1,75 @@ |
|||
# ABP.IO Platform 7.0 Final Has Been Released! |
|||
|
|||
[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 7.0 versions have been released today. |
|||
|
|||
## What's New With 7.0? |
|||
|
|||
Since all the new features are already explained in detail in the [7.0 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-7.0-RC-Has-Been-Published), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP.IO-Platform-7.0-RC-Has-Been-Published) for all the features and enhancements. |
|||
|
|||
## Getting Started with 7.0 |
|||
|
|||
### Creating New Solutions |
|||
|
|||
You can create a new solution with the ABP Framework version 7.0 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started). |
|||
|
|||
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. |
|||
|
|||
### How to Upgrade an Existing Solution |
|||
|
|||
#### Install/Update the ABP CLI |
|||
|
|||
First of all, install the ABP CLI or upgrade to the latest version. |
|||
|
|||
If you haven't installed it yet: |
|||
|
|||
```bash |
|||
dotnet tool install -g Volo.Abp.Cli |
|||
``` |
|||
|
|||
To update an existing installation: |
|||
|
|||
```bash |
|||
dotnet tool update -g Volo.Abp.Cli |
|||
``` |
|||
|
|||
#### Upgrading Existing Solutions with the ABP Update Command |
|||
|
|||
[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: |
|||
|
|||
```bash |
|||
abp update |
|||
``` |
|||
|
|||
Run this command in the root folder of your solution. |
|||
|
|||
## Migration Guides |
|||
|
|||
There are breaking changes in this version that may affect your application. Please see the following migration documents, if you are upgrading from v6.x: |
|||
|
|||
* [ABP Framework 6.x to 7.0 Migration Guide](https://docs.abp.io/en/abp/7.0/Migration-Guides/Abp-7_0) |
|||
* [ABP Commercial 6.x to 7.0 Migration Guide](https://docs.abp.io/en/commercial/7.0/migration-guides/v7_0) |
|||
|
|||
## Community News |
|||
|
|||
### Highlights from .NET 7.0? |
|||
|
|||
Our team has closely followed the ASP.NET Core and Entity Framework Core 7.0 releases, read Microsoft's guides and documentation and adapt the changes to our ABP.IO Platform. We are proud to say that we've shipped the ABP 7.0 RC.1 based on .NET 7.0 just after Microsoft's .NET 7.0 release. |
|||
|
|||
In addition to the ABP's .NET 7.0 upgrade, our team has created 13 great articles to highlight the important features coming with ASP.NET Core 7.0 and Entity Framework Core 7.0. |
|||
|
|||
You can read [this post](https://volosoft.com/Blog/Highlights-for-ASP.NET-Entity-Framework-Core-NET-7.0) to see the list of all articles. |
|||
|
|||
### New ABP Community Posts |
|||
|
|||
In addition to [the 13 articles to highlight .NET 7.0 features written by our team]((https://volosoft.com/Blog/Highlights-for-ASP.NET-Entity-Framework-Core-NET-7.0)), here are some of the recent posts added to the [ABP Community](https://community.abp.io/): |
|||
|
|||
* [liangshiwei](https://github.com/realLiangshiwei) has created a new community article, that shows [How to Use the Weixin Authentication for MVC / Razor Page Applications](https://community.abp.io/posts/how-to-use-the-weixin-authentication-for-mvc-razor-page-applications-a33e0wti). |
|||
* [Jasen Fici](https://community.abp.io/posts/deploying-abp.io-to-an-azure-appservice-ma8kukdp) has created a new article: [Deploying abp.io to an Azure AppService](https://community.abp.io/posts/deploying-abp.io-to-an-azure-appservice-ma8kukdp). |
|||
|
|||
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. |
|||
|
|||
## About the Next Version |
|||
|
|||
The next feature version will be 7.1. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). |
|||
|
|||
Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. |
|||
@ -0,0 +1,207 @@ |
|||
# ABP.IO Platform 7.1 RC Has Been Released |
|||
|
|||
Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.1 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. |
|||
|
|||
Try this version and provide feedback for a more stable version of ABP v7.1! Thanks to all of you. |
|||
|
|||
## Get Started with the 7.1 RC |
|||
|
|||
Follow the steps below to try version 7.1.0 RC today: |
|||
|
|||
1) **Upgrade** the ABP CLI to version `7.1.0-rc.1` using a command line terminal: |
|||
|
|||
````bash |
|||
dotnet tool update Volo.Abp.Cli -g --version 7.1.0-rc.1 |
|||
```` |
|||
|
|||
**or install** it if you haven't before: |
|||
|
|||
````bash |
|||
dotnet tool install Volo.Abp.Cli -g --version 7.1.0-rc.1 |
|||
```` |
|||
|
|||
2) Create a **new application** with the `--preview` option: |
|||
|
|||
````bash |
|||
abp new BookStore --preview |
|||
```` |
|||
|
|||
See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. |
|||
|
|||
> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application. |
|||
|
|||
You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). |
|||
|
|||
## Migrating to 7.1 |
|||
|
|||
This version doesn't introduce any breaking changes. However, Entity Framework developers may need to add a new code-first database migration to their projects since we made some improvements to the existing entities of some application modules. |
|||
|
|||
## What's New with ABP Framework 7.1? |
|||
|
|||
In this section, I will introduce some major features released in this version. In addition to these features, so many enhancements have been made in this version too. |
|||
|
|||
Here is a brief list of the titles explained in the next sections: |
|||
|
|||
* Blazor WASM option added to Application Single Layer Startup Template |
|||
* Introducing the `IHasEntityVersion` interface and `EntitySynchronizer` base class |
|||
* Introducing the `DeleteDirectAsync` method for the `IRepository` interface |
|||
* Introducing the `IAbpHostEnvironment` interface |
|||
* Improvements on the eShopOnAbp project |
|||
* Others |
|||
|
|||
### Blazor WASM option added to Application Single Layer Startup Template |
|||
|
|||
We've created the [Application (Single Layer) Startup Template](https://docs.abp.io/en/abp/7.1/Startup-Templates/Application-Single-Layer) in v5.2 with three UI types: Angular, Blazor Server and MVC. At the moment, we didn't provide UI option for Blazor, because it required 3 projects at least (server-side, client-side and shared library between these two projects). |
|||
|
|||
In this version, we've added the Blazor WASM option to the **Application (Single Layer) Startup Template**. It still contains three projects (`blazor`, `host`, and `contracts`) but hosted by a single `host` project. |
|||
|
|||
You can use the following CLI command to create an `app-nolayers` template with the Blazor UI as the UI option: |
|||
|
|||
```bash |
|||
abp new TodoApp -t app-nolayers -u blazor --version 7.1.0-rc.1 |
|||
``` |
|||
|
|||
> You can check the [Quick Start documentation](https://docs.abp.io/en/abp/7.1/Tutorials/Todo/Single-Layer/Index?UI=Blazor&DB=EF) for a quick start with this template. |
|||
|
|||
### Introducing the `IHasEntityVersion` interface and `EntitySynchronizer` base class |
|||
|
|||
Entity synchronization is an important concept, especially in distributed applications and module development. If we have an entity that is related to other modules, we need to align/sync their data once the entity changes and versioning entity changes can also be good, so we can know whether they're synced or not. |
|||
|
|||
In this version, [@gdlcf88](https://github.com/gdlcf88) made a great contribution to the ABP Framework and introduced the `IHasEntityVersion` interface which adds **auto-versioning** to entity classes and `EntitySynchronizer` base class to **automatically sync an entity's properties from a source entity**. |
|||
|
|||
You can check the issue and documentation from the following links for more info: |
|||
|
|||
- [Issue: Entity synchronizers and a new EntityVersion audit property](https://github.com/abpframework/abp/issues/14196) |
|||
- [Versioning Entities](https://docs.abp.io/en/abp/7.1/Entities#versioning-entities) |
|||
- [Distributed Event Bus - Entity Synchronizer](https://docs.abp.io/en/abp/7.1/Distributed-Event-Bus#entity-synchronizer) |
|||
|
|||
> Note: The entities of some modules from the ABP Framework have implemented the `IHasEntityVersion` interface. Therefore, if you are upgrading your application from an earlier version, you need to create a new migration and apply it to your database. |
|||
|
|||
### Introducing the `DeleteDirectAsync` method for the `IRepository` interface |
|||
|
|||
EF 7 introduced a new [`ExecuteDeleteAsync`](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) method that deletes entities without involving the change tracker into the process. Therefore, it's much faster. |
|||
|
|||
We've added the `DeleteDirectAsync` method to the `IRepository<>` interface to take the full power of EF 7. It deletes all entities that fit the given predicate. It directly deletes entities from the database, without fetching them. Therefore, some features (like **soft-delete**, **multi-tenancy**, and **audit logging)** won't work, so use this method carefully when you need it. And use the `DeleteAsync` method if you need those features. |
|||
|
|||
### Introducing the `IAbpHostEnvironment` interface |
|||
|
|||
Sometimes, while creating an application, we need to get the current hosting environment and take actions according to that. In such cases, we can use some services such as [IWebHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.iwebhostenvironment?view=aspnetcore-7.0) or [IWebAssemblyHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.hosting.iwebassemblyhostenvironment) provided by .NET, in the final application. |
|||
|
|||
However, we can not use these services in a class library, which is used by the final application. ABP Framework provides the `IAbpHostEnvironment` service, which allows you to get the current environment name whenever you want. `IAbpHostEnvironment` is used by the ABP Framework in several places to perform specific actions by the environment. For example, ABP Framework reduces the cache duration on the **Development** environment for some services. |
|||
|
|||
**Usage:** |
|||
|
|||
```csharp |
|||
public class MyService |
|||
{ |
|||
private readonly IAbpHostEnvironment _abpHostEnvironment; |
|||
|
|||
public MyService(IAbpHostEnvironment abpHostEnvironment) |
|||
{ |
|||
_abpHostEnvironment = abpHostEnvironment; |
|||
} |
|||
|
|||
public void MyMethod() |
|||
{ |
|||
//getting the current environment name |
|||
var environmentName = _abpHostEnvironment.EnvironmentName; |
|||
|
|||
//check for the current environment |
|||
if (_abpHostEnvironment.IsDevelopment()) { /* ... */ } |
|||
} |
|||
} |
|||
``` |
|||
|
|||
You can inject the `IAbpHostEnvironment` into your service and get the current environment by using its `EnvironmentName` property. You can also check the current environment by using its extension methods such as `IsDevelopment()`. |
|||
|
|||
> Check the [ABP Application Startup](https://docs.abp.io/en/abp/7.1/Application-Startup) documentation for more information. |
|||
|
|||
### Improvements on the eShopOnAbp project |
|||
|
|||
K8s and Docker configurations have been made within this version (Dockerfiles and helm-charts have been added and image build scripts have been updated). See [#14083](https://github.com/abpframework/abp/issues/14083) for more information. |
|||
|
|||
### Others |
|||
|
|||
* Referral Links have been added to the CMS Kit Comment Feature (optional). You can specify common referral links (such as "nofollow" and "noreferrer") for links in the comments. See [#15458](https://github.com/abpframework/abp/issues/15458) for more information. |
|||
* ReCaptcha verification has been added to the CMS Kit Comment Feature (optional). You can enable ReCaptcha support to enable protection against bots. See the [documentation](https://docs.abp.io/en/abp/7.1/Modules/Cms-Kit/Comments) for more information. |
|||
* In the development environment, it is a must to reduce cache durations for some points. We typically don't have to invalidate the cache manually or wait on it for a certain time to be invalidated. For that purpose, we have reduced the cache durations for some points on the development environment. See [#14842](https://github.com/abpframework/abp/pull/14842) for more information. |
|||
|
|||
## What's New with ABP Commercial 7.1? |
|||
|
|||
We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the new features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 7.1. |
|||
|
|||
### Blazor WASM option added to Application Single Layer Pro Startup Template |
|||
|
|||
The [**Application (Single Layer) Startup Template**](https://docs.abp.io/en/commercial/latest/startup-templates/application-single-layer/index) with Blazor UI is also available for ABP Commercial customers with this version as explained above. |
|||
|
|||
You can use the following CLI command to create an `app-nolayers-pro` template with Blazor UI as the UI option: |
|||
|
|||
```bash |
|||
abp new TodoApp -t app-nolayers-pro -u blazor --version 7.1.0-rc.1 |
|||
``` |
|||
|
|||
You can also create an `app-nolayers-pro` template with Blazor UI via ABP Suite: |
|||
|
|||
 |
|||
|
|||
### Suite - MAUI Blazor Code Generation |
|||
|
|||
We provided a new UI option "MAUI Blazor" for the `app-pro` template in the previous version and it's possible to create a `maui-blazor` application with both ABP CLI and ABP Suite. |
|||
|
|||
You can create an `app-pro` template with the MAUI Blazor as the UI option with the following ABP CLI command: |
|||
|
|||
```bash |
|||
abp new Acme.BookStore -t app-pro -u maui-blazor |
|||
``` |
|||
|
|||
In this version, we implemented the code generation for MAUI Blazor. You can create and generate CRUD pages for this new UI option as you do in other UI types. |
|||
|
|||
> Note: MAUI Blazor is currently only available with the `app-pro` template. |
|||
|
|||
### SaaS Module - Allowing entering a username while impersonating the tenant |
|||
|
|||
In the previous versions, we were able to impersonate a tenant from the [SaaS Module's Tenant Management UI](https://docs.abp.io/en/commercial/7.1/modules/saas#tenant-management). There was a constraint in this approach, which forced us to only impersonate the "admin" user. However, the tenant might change the admin user's username, or we may want to impersonate another user of the tenant. |
|||
|
|||
Thus, with this version, we decided to allow the impersonation of the tenant by the specified username. |
|||
|
|||
*You can click on the "Login with this tenant" action button:* |
|||
|
|||
 |
|||
|
|||
*Then, Specify the admin name of the tenant:* |
|||
|
|||
 |
|||
|
|||
## Community News |
|||
|
|||
### New ABP Community Posts |
|||
|
|||
* [Sergei Gorlovetsky](https://community.abp.io/members/Sergei.Gorlovetsky) has created two new community articles: |
|||
* [Why ABP Framework is one of the best tools for migration from legacy MS Access systems to latest Web app](https://community.abp.io/posts/why-abp-framework-is-one-of-the-best-tools-for-migration-from-legacy-ms-access-systems-to-latest-web-app-7l39eof0) |
|||
* [ABP Framework — 5 steps Go No Go Decision Tree](https://community.abp.io/posts/abp-framework-5-steps-go-no-go-decision-tree-2sy6r2st) |
|||
* [Onur Pıçakcı](https://github.com/onurpicakci) has created his first ABP community article that explains how to contribute to ABP Framework. You can read it 👉 [here](https://community.abp.io/posts/how-to-contribute-to-abp-framework-46dvzzvj). |
|||
* [Maliming](https://github.com/maliming) has created a new community article to show how to convert create/edit modals to a page. You can read it 👉 [here](https://community.abp.io/posts/converting-createedit-modal-to-page-4ps5v60m). |
|||
|
|||
We thank you all. We thank all the authors for contributing to the [ABP Community platform](https://community.abp.io/). |
|||
|
|||
### Volosoft Attended NDC London 2023 |
|||
|
|||
 |
|||
|
|||
Core team members of the ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) and [Alper Ebicoglu](https://twitter.com/alperebicoglu) attended [NDC London 2023](https://ndclondon.com/) from the 23rd to the 27th of January. |
|||
|
|||
### Community Talks 2023.1: LeptonX Customization |
|||
|
|||
 |
|||
|
|||
In this episode of ABP Community Talks, 2023.1; we'll talk about **LeptonX Customization**. We will dive into the details and show you how to customize the [LeptonX Theme](https://leptontheme.com/) with examples. |
|||
|
|||
The event will be live on Thursday, February 16, 2023 (20:00 - 21:00 UTC). |
|||
|
|||
> Register to listen and ask your questions now 👉 https://kommunity.com/volosoft/events/abp-community-talks-20231-leptonx-customization-03f9fd8c. |
|||
|
|||
## Conclusion |
|||
|
|||
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.1/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try the ABP v7.1 RC and provide feedback to help us release a more stable version. |
|||
|
|||
Thanks for being a part of this community! |
|||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 80 KiB |
@ -0,0 +1,71 @@ |
|||
# Bulk Operations with Entity Framework Core 7.0 |
|||
Entity Framework tracks all the entity changes and applies those changes to the database one by one when the `SaveChanges()` method is called. There was no way to execute bulk operations in Entity Framework Core without a dependency. |
|||
|
|||
As you know the [Entity Framework Extensions](https://entityframework-extensions.net/bulk-savechanges) library was doing it but it was not free. |
|||
|
|||
There was no other solution until now. [Bulk Operations](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) are now available in Entity Framework Core with .NET 7. |
|||
|
|||
With .NET 7, there are two new methods such as `ExecuteUpdate` and `ExecuteDelete` available to execute bulk operations. It's a similar usage with the Entity Framework Core Extensions library if you're familiar with it. |
|||
|
|||
You can visit the microsoft example [here](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) about how to use it. |
|||
|
|||
|
|||
It can be easily used with the DbContext. |
|||
|
|||
```csharp |
|||
await context.Tags.Where(t => t.Text.Contains(".NET")).ExecuteDeleteAsync(); |
|||
``` |
|||
|
|||
## Using with ABP Framework |
|||
ABP Framework provides an abstraction over database operations and implements generic repository pattern. So, DbContext can't be accessed outside of [repositories](https://docs.abp.io/en/abp/latest/Repositories). |
|||
|
|||
You can use the `ExecuteUpdate` and `ExecuteDelete` methods inside a repository. |
|||
|
|||
```csharp |
|||
public class BookEntityFrameworkCoreRepository : EfCoreRepository<BookStoreDbContext, Book, Guid>, IBookRepository |
|||
{ |
|||
public BookEntityFrameworkCoreRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider) : base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public async Task UpdateListingAsync() |
|||
{ |
|||
var dbSet = await GetDbSetAsync(); |
|||
|
|||
await dbSet |
|||
.Where(x => x.IsListed && x.PublishedOn.Year <= 2022) |
|||
.ExecuteUpdateAsync(s => s.SetProperty(x => x.IsListed, x => false)); |
|||
} |
|||
|
|||
public async Task DeleteOldBooksAsync() |
|||
{ |
|||
var dbSet = await GetDbSetAsync(); |
|||
|
|||
await dbSet |
|||
.Where(x => x.PublishedOn.Year <= 2000) |
|||
.ExecuteDeleteAsync(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
There is no need to take an action for bulk inserting. You can use the `InsertManyAsync` method of the repository instead of creating a new method for it if you don't have custom logic. It'll use a new bulk inserting feature automatically since it's available in EF Core 7.0. |
|||
|
|||
```csharp |
|||
public class MyDomainService : DomainService |
|||
{ |
|||
protected IRepository<Book, Guid> BookRepository { get; } |
|||
|
|||
public MyDomainService(IRepository<Book, Guid> bookRepository) |
|||
{ |
|||
BookRepository = bookRepository; |
|||
} |
|||
|
|||
public async Task CreateBooksAsync(List<Book> books) |
|||
{ |
|||
// It'll use bulk inserting automatically. |
|||
await BookRepository.InsertManyAsync(books); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> If you use `ExecuteDeleteAsync` or `ExecuteUpdateAsync`, then ABP's soft delete and auditing features can not work. Because these ABP features work with EF Core's change tracking system and these new methods doesn't work with the change tracking system. So, use them carefully. |
|||
@ -0,0 +1,208 @@ |
|||
# Value generation for DDD guarded types with Entity Framework Core 7.0 |
|||
|
|||
In domain-driven design (DDD), *guarded keys* can improve the type safety of key properties. This is achieved by wrapping the key type in another type which is specific to the use of the key. In this article, I will explain the cases why you may need to use guarded types and discuss the advantages and limitations when implementing to an ABP application. |
|||
|
|||
> You can find the source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo). |
|||
|
|||
## The Problem |
|||
|
|||
While developing an applications, there are many cases where we manually assign foreign keys that can be in guid type or integer type, etc. This manual assignment mistakes can cause miss-match of unique identifiers, such as **assigning a product ID to a category**, that can be hard to detect in the future. |
|||
|
|||
Here is a very simplified sample of wrong assignment when trying to update a product category: |
|||
|
|||
````csharp |
|||
public class ProductAppService : MyProductStoreAppService, IProductAppService |
|||
{ |
|||
private readonly IRepository<Product, Guid> _productRepository; |
|||
|
|||
public ProductAppService(IRepository<Product, Guid> productRepository) |
|||
{ |
|||
_productRepository = productRepository; |
|||
} |
|||
|
|||
public async Task UpdateProductCategoryAsync(Guid productId, Guid categoryId) |
|||
{ |
|||
var productToUpdate = await _productRepository.GetAsync(productId); |
|||
productToUpdate.CategoryId = productId; // Wrong assignment that causes error only at run-time |
|||
|
|||
await _productRepository.UpdateAsync(productToUpdate); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
While the sample demonstrates a very simple mistake, it is easier to come across similar mistakes when the business logic gets more complex especially when you are using methods with **multiple foreign key arguments**. The next section offers using guarded types to prevent these kind of problems as a solution to the problem. |
|||
|
|||
## The Solution |
|||
|
|||
Strongly-typed IDs (*guarded keys*) is a DDD approach to address this problem. One of the main problems with .NET users was handling the persisting these objects. With EFCore7, key properties can be guarded with type safety seamlessly. |
|||
|
|||
To use guarded keys, update your aggregate root or entity unique identifier with a complex type to overcome *primitive obsession*: |
|||
|
|||
````csharp |
|||
public readonly struct CategoryId |
|||
{ |
|||
public CategoryId(Guid value) => Value = value; |
|||
public Guid Value { get; } |
|||
} |
|||
|
|||
public readonly struct ProductId |
|||
{ |
|||
public ProductId(Guid value) => Value = value; |
|||
public Guid Value { get; } |
|||
} |
|||
```` |
|||
|
|||
You can now use these keys for your aggregate roots or entities: |
|||
|
|||
```csharp |
|||
public class Product : AggregateRoot<ProductId> |
|||
{ |
|||
public ProductId Id { get; set; } |
|||
public string Name { get; set; } |
|||
public CategoryId CategoryId { get; set; } |
|||
|
|||
private Product() { } |
|||
|
|||
public Product(ProductId id, string name) : base(id) |
|||
{ |
|||
Name = Check.NotNullOrEmpty(name, nameof(name)); |
|||
} |
|||
} |
|||
|
|||
public class Category : AggregateRoot<CategoryId> |
|||
{ |
|||
public CategoryId Id { get; set; } |
|||
public string Name { get; set; } |
|||
public List<Product> Products { get; } = new(); |
|||
|
|||
private Category() { } |
|||
|
|||
public Category(CategoryId id, string name) : base(id) |
|||
{ |
|||
Name = Check.NotNullOrEmpty(name, nameof(name)); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
`ProductId` and `CategoryId` guarded key types shown in the sample use `Guid` key values, which means Guid values will be used in the mapped database tables. This is achieved by defining [value converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions) for the types. |
|||
|
|||
Override the `ConfigureConventions` method of your DbContext to use the value converters: |
|||
|
|||
````csharp |
|||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) |
|||
{ |
|||
configurationBuilder.Properties<ProductId>().HaveConversion<ProductIdConverter>(); |
|||
configurationBuilder.Properties<CategoryId>().HaveConversion<CategoryIdConverter>(); |
|||
} |
|||
|
|||
private class ProductIdConverter : ValueConverter<ProductId, Guid> |
|||
{ |
|||
public ProductIdConverter() |
|||
: base(v => v.Value, v => new(v)) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
private class CategoryIdConverter : ValueConverter<CategoryId, Guid> |
|||
{ |
|||
public CategoryIdConverter() |
|||
: base(v => v.Value, v => new(v)) |
|||
{ |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> The code here uses `struct` types. This means they have appropriate value-type semantics for use as keys. If `class` types are used instead, then they need to either override equality semantics or also specify a [value comparer](https://learn.microsoft.com/en-us/ef/core/modeling/value-comparers). |
|||
|
|||
Now, you can use generic (or custom) repositories of ABP using the guarded type as the key for the repository: |
|||
|
|||
```csharp |
|||
public class ProductStoreDataSeedContributor : IDataSeedContributor, ITransientDependency |
|||
{ |
|||
private readonly IRepository<Category, CategoryId> _categoryRepository; |
|||
private readonly IRepository<Product, ProductId> _productRepository; |
|||
|
|||
public ProductStoreDataSeedContributor( |
|||
IRepository<Category, CategoryId> categoryRepository, |
|||
IRepository<Product, ProductId> productRepository |
|||
) |
|||
{ |
|||
_categoryRepository = categoryRepository; |
|||
_productRepository = productRepository; |
|||
} |
|||
|
|||
// ... |
|||
} |
|||
``` |
|||
|
|||
You can also use `integer` as guarded type for your key properties and use [Sequence-based key generation for SQL Server](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#sequence-based-key-generation-for-sql-server) for value generation. |
|||
|
|||
## Discussions |
|||
|
|||
In this section, I will discuss the use cases of guarded types and limitations when implementing to an ABP application. |
|||
|
|||
### Do I need to use guarded types? |
|||
|
|||
If you are already following the best practices of [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design), you are aware that **updates** and **creations** of an aggregate are done **over** the aggregate root itself as a whole unit. And entity state changes of an aggregate root should be done using the [domain services](https://docs.abp.io/en/abp/latest/Domain-Services). Domain services should already validate the entity. |
|||
|
|||
**Example: Using domain service to update product:** |
|||
|
|||
````csharp |
|||
public class ProductManager : DomainService |
|||
{ |
|||
private readonly IRepository<Product, Guid> _productRepository; |
|||
|
|||
public ProductManager(IRepository<Product, Guid> productRepository) |
|||
{ |
|||
_productRepository = productRepository; |
|||
} |
|||
|
|||
public Task<Product> AssignCategory(Product product, Category category) |
|||
{ |
|||
// ... |
|||
|
|||
product.CategoryId = category.Id; |
|||
|
|||
//.. |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In this sample, domain service validates that both **product** and the **category** entities, passed by the application layer, are valid objects since they are not key properties. However, manual assignment is already in place and more complex the domain logic, higher to miss out mistakes. At the end, it will depend on your tolerance level for developer errors comparing to the time you want to spend time on additional code base for guarded types. |
|||
|
|||
### Limitations |
|||
|
|||
One important limitation is automatic value generation when using `Guid` as guarded type for your key properties. The basic repository can not generate the unique identifier automatically by the time this article is written: |
|||
|
|||
```csharp |
|||
public readonly struct ProductId |
|||
{ |
|||
public ProductId(Guid value) => Value = value; |
|||
public Guid Value { get; } |
|||
} |
|||
``` |
|||
|
|||
you need to generate the unique identifier **manually**: |
|||
|
|||
````csharp |
|||
var newProduct = await _productRepository.InsertAsync( |
|||
new Product(new ProductId(_guidGenerator.Create()), "New product") |
|||
); |
|||
```` |
|||
|
|||
## Conclusion |
|||
|
|||
In this article, I tried to explain DDD guarded types for key properties and value generation for these properties using Entity Framework 7.0 and ABP. |
|||
|
|||
Using strongly-typed key properties reduce the chance of unnoticed errors. Admittedly it increases the code complexity by adding new types to your solution with extra coding, especially if you are using classes as keys. Guarded types provide improved safety for your code base at the expense of additional code complexity as in many DDD concepts and patterns. |
|||
|
|||
If you have a large team working on a large scale solution containing complex business logics where key assignments are abundant or if you are using methods with multiple foreign key arguments, I personally suggest using guarded types. |
|||
|
|||
## The Source Code |
|||
|
|||
* You can find the full source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo). |
|||
|
|||
## See Also |
|||
|
|||
* [What's new in EF Core 7.0](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew) |
|||
* [ABP Framework: Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) |
|||
@ -0,0 +1,231 @@ |
|||
# Rate Limiting with ASP.NET Core 7.0 |
|||
|
|||
Rate limiting is a way of controlling the traffic that a web application or API receives. In other words, rate limiting helps you control the amount of traffic each user has access to at any given time. This is extremely useful when you want to manage the load on your server or services, avoid going over your monthly data transfer limit and allow the system to continue to function and meet service level agreements, even when an increase in demand places an extreme load on resources. |
|||
|
|||
In this article, we will look at what rate limiting is, why we need to use it, how the different rate limiting algorithms provided with .NET 7.0 work, and best practices for using rate limiting in your application. |
|||
|
|||
## What is rate limiting? |
|||
|
|||
Whether accidental or intentional, users may exhaust resources in a way that impacts others. When a number of requests are received on to resources for a long time, the server can run out of those resources. These resources can include memory, threads, connections, or anything else that is limited. To avoid this situation, set rate limiters. Rate limiters control the consumption of resources used by an instance of an application, a user, an individual tenant, or an entire service. |
|||
|
|||
## Why do you need to use rate limiting? |
|||
|
|||
A rate limiting system is crucial in any application where you have to control or throttle user requests or traffic. This is especially true in applications running on a cloud hosting platform because the user’s traffic can affect the whole server where the application is hosted. |
|||
|
|||
Why do you need to implement rate limiting? Here are a few reasons: |
|||
|
|||
- To ensure that a system continues to meet service level agreements (SLA). |
|||
- To prevent a single user, tenant, service, or so on from monopolizing the resources provided by an application. |
|||
- To help cost-optimize a system by limiting the maximum resource levels needed to keep it functioning. |
|||
|
|||
## Rate limiter algorithms |
|||
|
|||
The [`RateLimiterOptionsExtensions`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.ratelimiting.ratelimiteroptionsextensions) class provides the following extension methods for rate limiting: |
|||
|
|||
- **[Fixed window](https://devblogs.microsoft.com/dotnet/announcing-rate-limiting-for-dotnet/#fixed-window-limit)**: Fixed-window limits—such as 3,000 requests per hour or 10 requests per day—are easy to state, but they are subject to spikes at the edges of the window, as available quota resets. Consider, for example, a limit of 3,000 requests per hour, which still allows for a spike of all 3,000 requests to be made in the first minute of the hour, which might overwhelm the service. |
|||
- [**Sliding window**:](https://devblogs.microsoft.com/dotnet/announcing-rate-limiting-for-dotnet/#sliding-window-limit) Sliding windows have the benefits of a fixed window, but the rolling window of time smoothes out bursts. Systems such as Redis facilitate this technique with expiring keys. |
|||
- [**Token bucket**](https://devblogs.microsoft.com/dotnet/announcing-rate-limiting-for-dotnet/#token-bucket-limit): A token bucket maintains a rolling and accumulating budget of usage as a balance of tokens. A token bucket adds tokens at some rate. When a service request is made, the service attempts to withdraw a token (decrementing the token count) to fulfill the request. If there are no tokens in the bucket, the service has reached its limit and responds with backpressure. |
|||
- [**Concurrency**](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?preserve-view=true&view=aspnetcore-7.0#concurrency-limiter): A concurrency limiter is the simplest form of rate limiting. It doesn’t look at time, just at number of concurrent requests. |
|||
|
|||
In order to be a more realistic example, instead of making an example with each rate limiter algorithm, we will implement the following three algorithms in an **ABP-based** application. |
|||
|
|||
1. We will add a `SlidingWindowLimiter` with a partition for all anonymous users. |
|||
2. We will add a `TokenBucketRateLimiter` with a partition for each authenticated user. |
|||
3. We will add a `ConcurrencyLimiter` with a partition for each Tenant. |
|||
|
|||
**Note:** The following sample isn't meant for production code but is an example of how to use the limiters in ABP-based applications. |
|||
|
|||
### Limiter with `OnRejected`, `RetryAfter`, and `GlobalLimiter` |
|||
|
|||
#### Add rate limiter |
|||
|
|||
Let's create the following method in the `MyProjectNameWebModule.cs` class in the `MyProjectName.Web` project. |
|||
|
|||
**Note:** If the `**.Web` project is not in your application, you can do the same in the project where your application is hosted. |
|||
|
|||
```csharp |
|||
private void ConfigureRateLimiters(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddRateLimiter(limiterOptions => |
|||
{ |
|||
limiterOptions.OnRejected = (context, cancellationToken) => |
|||
{ |
|||
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) |
|||
{ |
|||
context.HttpContext.Response.Headers.RetryAfter = |
|||
((int) retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo); |
|||
} |
|||
|
|||
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; |
|||
context.HttpContext.RequestServices.GetService<ILoggerFactory>()? |
|||
.CreateLogger("Microsoft.AspNetCore.RateLimitingMiddleware") |
|||
.LogWarning("OnRejected: {RequestPath}", context.HttpContext.Request.Path); |
|||
|
|||
return new ValueTask(); |
|||
}; |
|||
|
|||
limiterOptions.AddPolicy("UserBasedRateLimiting", context => |
|||
{ |
|||
var currentUser = context.RequestServices.GetService<ICurrentUser>(); |
|||
|
|||
if (currentUser is not null && currentUser.IsAuthenticated) |
|||
{ |
|||
return RateLimitPartition.GetTokenBucketLimiter(currentUser.UserName, _ => new TokenBucketRateLimiterOptions |
|||
{ |
|||
TokenLimit = 10, |
|||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst, |
|||
QueueLimit = 3, |
|||
ReplenishmentPeriod = TimeSpan.FromMinutes(1), |
|||
TokensPerPeriod = 4, |
|||
AutoReplenishment = true |
|||
}); |
|||
} |
|||
|
|||
return RateLimitPartition.GetSlidingWindowLimiter("anonymous-user", |
|||
_ => new SlidingWindowRateLimiterOptions |
|||
{ |
|||
PermitLimit = 2, |
|||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst, |
|||
QueueLimit = 1, |
|||
Window = TimeSpan.FromMinutes(1), |
|||
SegmentsPerWindow = 2 |
|||
}); |
|||
}); |
|||
|
|||
limiterOptions.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context => |
|||
{ |
|||
var currentTenant = context.RequestServices.GetService<ICurrentTenant>(); |
|||
|
|||
if (currentTenant is not null && currentTenant.IsAvailable) |
|||
{ |
|||
return RateLimitPartition.GetConcurrencyLimiter(currentTenant!.Name, _ => new ConcurrencyLimiterOptions |
|||
{ |
|||
PermitLimit = 5, |
|||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst, |
|||
QueueLimit = 1 |
|||
}); |
|||
} |
|||
|
|||
return RateLimitPartition.GetNoLimiter("host"); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
In the above example, the `TokenBucketLimiter` is used for each authenticated user, while the `SlidingWindowLimiter` is used for all anonymous users. Additionally, as a global limiter, the `ConcurrencyLimiter` is used for each tenant, while rate limiting is disabled for the host(tenant is not available). Also, for requests that are rejected when the limit is reached, sets the response status code to [429 Too Many Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) and the response mentions when to retry (if available from the rate-limiting metadata). |
|||
|
|||
Let's call the `ConfigureRateLimiters` method that we created in the `ConfigureServices` method. |
|||
|
|||
The final version of the `ConfigureServices` method: |
|||
|
|||
```csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
ConfigureBundles(); |
|||
ConfigureUrls(configuration); |
|||
ConfigurePages(configuration); |
|||
ConfigureAuthentication(context); |
|||
ConfigureImpersonation(context, configuration); |
|||
ConfigureAutoMapper(); |
|||
ConfigureVirtualFileSystem(hostingEnvironment); |
|||
ConfigureNavigationServices(); |
|||
ConfigureAutoApiControllers(); |
|||
ConfigureSwaggerServices(context.Services); |
|||
ConfigureExternalProviders(context); |
|||
ConfigureHealthChecks(context); |
|||
ConfigureCookieConsent(context); |
|||
ConfigureTheme(); |
|||
|
|||
Configure<PermissionManagementOptions>(options => |
|||
{ |
|||
options.IsDynamicPermissionStoreEnabled = true; |
|||
}); |
|||
|
|||
ConfigureRateLimiters(context); // added |
|||
} |
|||
``` |
|||
|
|||
#### Add RateLimiter middleware |
|||
|
|||
Add the following line just before the `app.UseConfiguredEndpoints(...)` line to add the `RateLimiter` middleware to your ASP.NET Core request pipeline: |
|||
|
|||
```csharp |
|||
app.UseRateLimiter(); |
|||
``` |
|||
|
|||
#### Use rate limiter for all controllers |
|||
|
|||
Let's edit the `ConfiguredEndpoints` middleware as follows: |
|||
|
|||
```csharp |
|||
app.UseConfiguredEndpoints(endpoints => |
|||
{ |
|||
endpoints.MapRazorPages() |
|||
.DisableRateLimiting(); |
|||
|
|||
endpoints.MapControllers() |
|||
.RequireRateLimiting("UserBasedRateLimiting"); |
|||
}); |
|||
``` |
|||
|
|||
- **DisableRateLimiting:** It is used to disable the `ConcurrencyLimiter` for razor pages, which we set globally when the tenant is available. |
|||
- **RequireRateLimiting:** We have enabled the rate limiter, which we define according to whether the user is authenticated or not, for all controllers. |
|||
|
|||
## `EnableRateLimiting` and `DisableRateLimiting` attributes |
|||
|
|||
It's kind of unrealistic to always use rate limiting for all controllers or pages. Sometimes, we may want to throttle a particular endpoint or page. In such cases, we can use the `EnableRateLimiting` and `DisableRateLimiting` attributes. The `EnableRateLimiting` and `DisableRateLimiting` attributes can be applied to a controller, action method, or razor rage. Check [here](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?preserve-view=true&view=aspnetcore-7.0#enableratelimiting-and-disableratelimiting-attributes) for more. |
|||
|
|||
## Rate limit an HTTP handler |
|||
|
|||
Rate limiting when sending an HTTP request can be a good practice, especially in service-to-service communication. Because, resources are consumed by apps that rely on them, and when an app makes too many requests for a single resource, it can lead to *resource contention*. Resource contention occurs when a resource is consumed by too many clients, and the resource is unable to serve all of the apps that are requesting it. This can result in a poor user experience, and in some cases, it can even lead to a denial of service (DoS) attack. Since there are similar codes, I will not mention an example in this article, but to avoid such situations, you can write your own HTTP handler as [here](https://learn.microsoft.com/en-us/dotnet/core/extensions/http-ratelimiter#implement-a-delegatinghandler-subclass). |
|||
|
|||
## How does it work? |
|||
|
|||
[System.Threading.RateLimiting](https://www.nuget.org/packages/System.Threading.RateLimiting) provides the primitives for writing rate limiters as well as providing a few commonly used algorithms built-in. The main type is the abstract base class [RateLimiter](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/RateLimiter.cs). |
|||
|
|||
```csharp |
|||
public abstract class RateLimiter : IAsyncDisposable, IDisposable |
|||
{ |
|||
public abstract int GetAvailablePermits(); |
|||
public abstract TimeSpan? IdleDuration { get; } |
|||
|
|||
public RateLimitLease Acquire(int permitCount = 1); |
|||
public ValueTask<RateLimitLease> WaitAsync(int permitCount = 1, CancellationToken cancellationToken = default); |
|||
|
|||
public void Dispose(); |
|||
public ValueTask DisposeAsync(); |
|||
} |
|||
``` |
|||
|
|||
`RateLimiter` contains `Acquire` and `WaitAsync` as the core methods for trying to gain permits for a resource that is being protected. Depending on the application, the protected resource may need to acquire more than 1 permits, so `Acquire` and `WaitAsync` both accept an optional `permitCount` parameter. `Acquire` is a synchronous method that will check if enough permits are available or not and return a `RateLimitLease` which contains information about whether you successfully acquired the permits or not. `WaitAsync` is similar to `Acquire` except that it can support queuing permit requests which can be de-queued at some point in the future when the permits become available, which is why it’s asynchronous and accepts an optional `CancellationToken` to allow canceling the queued request. |
|||
|
|||
`RateLimitLease` has an `IsAcquired` property which is used to see if the permits were acquired. Additionally, the `RateLimitLease` may contain metadata such as a suggested retry-after period if the lease failed. Finally, the `RateLimitLease` is disposable and should be disposed when the code is done using the protected resource. The disposal will let the `RateLimiter` know to update its limits based on how many permits were acquired. |
|||
|
|||
## Limitations |
|||
|
|||
In most cases, the rate-limiting middleware provided with ASP.NET 7.0 will meet your requirements. However, if you would want to return statistics about your limits (e.g. [the way GitHub does](https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#rate-limit-http-headers)), you’ll find out that the ASP.NET rate limiting middleware does not support this. You won’t have access to the “number of requests remaining” or other metadata. Not in `OnRejected`, and definitely not if you want to return this data as headers on every request. |
|||
|
|||
## Best practices for rate limiting |
|||
|
|||
In order to use rate limiting properly, you need to have a solid understanding of the types of limiting available, as well as the data rate and data volume of your service. You also need to have a clear idea of how many users you expect to use your service as well as how they will interact with it. The best practices for rate limiting are as follows: |
|||
- Find the right rate limiter algorithm for your endpoint. I mean, the cost of an endpoint should be considered when selecting a limiter. The cost of an endpoint includes the resources used, for example, time, data access, CPU, and I/O. |
|||
- Set realistic limits. Once you’ve figured out all the above, you need to set realistic limits for each service. Then, before deploying an app using rate limiting to production, stress test the app to validate the rate limiters and options used. For example, create a [JMeter script](https://jmeter.apache.org/usermanual/jmeter_proxy_step_by_step.html) with a tool like [BlazeMeter](https://guide.blazemeter.com/hc/articles/207421695-Writing-your-first-JMeter-script) or [Apache JMeter HTTP(S) Test Script Recorder](https://jmeter.apache.org/usermanual/jmeter_proxy_step_by_step.html) and load the script to [Azure Load Testing](https://learn.microsoft.com/en-us/azure/load-testing/overview-what-is-azure-load-testing). |
|||
- In response to rate-limiting, intermittent, or non-specific errors, a client should generally retry the request after a delay. It is a best practice for this delay to increase exponentially after each failed request, which is referred to as *exponential backoff*. When many clients might be making schedule-based requests (such as fetching results every hour), additional random time (*jitter*) should be applied to the request timing, the backoff period, or both of them to ensure that these multiple client instances don't become periodic [thundering herd](https://www.wikiwand.com/en/Thundering_herd_problem), and cause a form of DDoS themselves. |
|||
|
|||
## Conclusion |
|||
|
|||
In this article, we’ve covered what rate limiting is, why you need to use it and the best practices for doing so. We’ve also looked at how to use three rate-limiting algorithms that are provided with .NET 7.0 on ABP-based applications and how rate-limiting works. Now that you’re familiar with the concept of rate limiting, it’s time to start implementing rate limiting in your application. This will allow you to control the traffic and ensure that your application is running smoothly without any issues. |
|||
|
|||
## References |
|||
|
|||
- https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?preserve-view=true&view=aspnetcore-7.0 |
|||
- https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/ |
|||
- https://blog.maartenballiauw.be/post/2022/09/26/aspnet-core-rate-limiting-middleware.html |
|||
- https://learn.microsoft.com/en-us/dotnet/core/extensions/http-ratelimiter |
|||
- https://learn.microsoft.com/en-us/azure/architecture/patterns/rate-limiting-pattern |
|||
- https://learn.microsoft.com/en-us/azure/architecture/patterns/throttling |
|||
- https://devblogs.microsoft.com/dotnet/announcing-rate-limiting-for-dotnet |
|||
- https://cloud.google.com/architecture/rate-limiting-strategies-techniques |
|||
|
After Width: | Height: | Size: 625 KiB |
@ -0,0 +1,68 @@ |
|||
# How to Use the Weixin Authentication for MVC / Razor Page Applications |
|||
|
|||
This guide demonstrates how to integrate Weixin to an ABP application that enables users to sign in using OAuth 2.0 with credentials. |
|||
|
|||
## Create a sandbox account |
|||
|
|||
If you don't have a production account, you can create a sendbox account for testing: https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index |
|||
|
|||
In this article we will use the sandbox account. |
|||
|
|||
> You should configure the callback domain name on the Weixin open platform |
|||
|
|||
## AddWeixin |
|||
|
|||
You need to install `AspNet.Security.OAuth.Weixin` package to your **.Web** project. |
|||
|
|||
In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following: |
|||
|
|||
```csharp |
|||
private void ConfigureAuthentication(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); |
|||
context.Services.AddAuthentication() |
|||
.AddWeixin(options => |
|||
{ |
|||
options.ClientId = configuration["Weixin:ClientId"]; |
|||
options.ClientSecret = configuration["Weixin:ClientSecret"]; |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
Updating `appsettings.json` to add `Weixin` section: |
|||
|
|||
````json |
|||
"Weixin": { |
|||
"ClientId": "<your-app-id>", |
|||
"ClientSecret": "<your-app-secret>" |
|||
} |
|||
```` |
|||
|
|||
## Web page authorization |
|||
|
|||
Now you can run the application to login with Weixin. |
|||
|
|||
 |
|||
|
|||
It will redirect to weixin platform to scan the QR code. |
|||
|
|||
> The sandbox account lacks the necessary scope, so it may not work properly. |
|||
|
|||
## Official account authorization |
|||
|
|||
Updating `AddWeixin`: |
|||
|
|||
```csharp |
|||
context.Services.AddAuthentication() |
|||
.AddWeixin(options => |
|||
{ |
|||
options.ClientId = configuration["Weixin:ClientId"]; |
|||
options.ClientSecret = configuration["Weixin:ClientSecret"]; |
|||
options.AuthorizationEndpoint = "https://open.weixin.qq.com/connect/oauth2/authorize"; |
|||
}); |
|||
``` |
|||
|
|||
Now you can use WeChat app to open the web application URL to login with weixin. |
|||
|
|||
 |
|||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 25 KiB |
@ -0,0 +1,105 @@ |
|||
# How to Contribute to ABP Framework |
|||
|
|||
## Introduction |
|||
|
|||
In this article I will explain how you can contribute to the open source ABP Framework. You will not only learn about the ABP Framework, but also how to contribute to an open source project, what are the standard rules, some git operations, etc. |
|||
|
|||
## What is Open Source? |
|||
|
|||
Open source software is code designed to be publicly available. Anyone can view, use, modify and distribute the project and code. The fact that the code is open source makes it a natural community and open for improvement. This enables ideas and thoughts to spread rapidly. |
|||
|
|||
## What is ABP Framework? |
|||
|
|||
ABP Framework is a complete infrastructure for building modern web applications following the best practices and guidelines of software development. ABP Framework is completely free, [open source](https://github.com/abpframework) and community driven. ABP is a modular framework and Application Modules provide pre-built application functionalities. |
|||
|
|||
## Before Contribution |
|||
|
|||
Before making any changes and trying to push them to the target repository we need to create a new [issue](https://github.com/abpframework/abp/issues) if there are no issues with the work. If there is an existing issue, you can proceed through this issue. This way, no other developer will work on the same issue and your PR will have a better chance to be accepted. |
|||
|
|||
Previous ABP Community Talk on this topic can be found [here](https://www.youtube.com/watch?v=Wz4Z-O-YoPg). |
|||
|
|||
## GitHub Issues |
|||
You may want to fix a known bug or work on a planned enhancement. See the [issue list](https://github.com/abpframework/abp/issues) on GitHub. |
|||
|
|||
## Feature Requests |
|||
If you have a feature idea for the framework or modules, create an issue on GitHub or attend an existing discussion. Then you can implement it if it's embraced by the community. |
|||
|
|||
## How to Contribute to an Open Source Software? |
|||
There are some steps to contribute to OSS projects. You can follow the steps below. |
|||
|
|||
## Step 1: Fork the Project |
|||
|
|||
The first thing we need to do now is to fork the open source project. Forking will create a copy of the project in your own GitHub account. This will allow users to make changes to the code without affecting the original repository. Just press the fork key in the project. |
|||
|
|||
 |
|||
|
|||
After forking, it will create a new repo in your own GitHub profile. |
|||
|
|||
 |
|||
|
|||
## Step 2: Clone the Project |
|||
|
|||
In order to develop the project, you need to clone it to your local. After clicking on the code button, select your preferred cloning method and copy the link. You can run the copied link on your local machine with the `git clone` command, but we will use GitHub Desktop. Press `Open with GitHub Desktop` and the repo will be installed on your local machine. |
|||
|
|||
or alternatively use the `git clone https://github.com/username/abp.git` command |
|||
|
|||
 |
|||
|
|||
## Step 3: Create a New Branch |
|||
|
|||
In this step, you need to create a new branch of your own before you start developing it. Open the repo on GitHub Desktop and create a new branch. When creating a new branch, be careful which branch you create it on. |
|||
|
|||
or alternatively use the `git checkout -b new-branch` command |
|||
|
|||
 |
|||
|
|||
## Step 4: Development |
|||
|
|||
Choose a suitable IDE to develop on the new branch you created. In order not to complicate things, we will create a `Developers.md` file and process it. Let's enter a sample text in the Developers file. |
|||
|
|||
 |
|||
|
|||
As you can see, all changes made to the repo are reflected directly on GitHub Desktop. |
|||
|
|||
 |
|||
|
|||
## Step 5: Commit |
|||
|
|||
The commit operation is used to save the changes you have made. It is useful to commit after certain operations are done in the project. It is useful to write a short sentence describing what you've done for the changes made in each commit. Press the `Commit to <branch-name>` button to commit. |
|||
|
|||
or alternatively use the `git add .` and `git commit -m "Added the Developer List"` command |
|||
|
|||
 |
|||
|
|||
## Step 6: Publish the Changes |
|||
|
|||
The changes you have made so far are only visible on your local machine. You need to publish these changes to submit them to your forked repository. Please press the publish branch button to publish. |
|||
|
|||
or alternatively use the `git push origin new-branch` command |
|||
|
|||
|
|||
 |
|||
|
|||
## Step 7: Create a Pull Request |
|||
|
|||
After the push, the pull request `Create Pull Request` button will appear on GitHub Desktop. Click it and create a pull request. |
|||
|
|||
 |
|||
|
|||
You can also make a pull request from the repo in your GitHub profile. |
|||
|
|||
 |
|||
|
|||
Before creating the pull request, make sure that the branch you created is making changes to the correct branch. After briefly describing your changes in the title and description, click the `Create pull request` button. This will send a pull request to the original repository. If the pull request is approved and merged by the community, your changes will also appear in the main repository. |
|||
|
|||
 |
|||
|
|||
That's it! You have contributed your development to an open source project. |
|||
|
|||
## Conclusion |
|||
In this article, I showed you how you could contribute to the ABP Framework, an open source and community driven project. Thank you for reading the article, I hope it was useful. See you soon! |
|||
|
|||
## References |
|||
- https://opensource.guide/how-to-contribute/ |
|||
- https://docs.abp.io/en/abp/latest/Contribution/Index |
|||
|
|||
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 666 KiB |
|
After Width: | Height: | Size: 550 KiB |
|
After Width: | Height: | Size: 664 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 37 KiB |
@ -0,0 +1,86 @@ |
|||
# Converting Create/Edit Modal to Page |
|||
|
|||
In this document we will explain how to convert BookStore's `Books` create & edit modals to regular razor pages. |
|||
|
|||
## Before |
|||
 |
|||
|
|||
## Now |
|||
 |
|||
|
|||
## Index page |
|||
|
|||
Repalce `abp-button(NewBookButton)` buttom with `<a class="btn btn-primary" href="/Books/CreateModal"><i class="fa fa-plus"></i> @L["NewBook"].Value</a>`. |
|||
|
|||
## Index js file |
|||
|
|||
Remove the related codes of `createModal` and `editModal`. |
|||
|
|||
Change the `Edit row action` with `location.href = "/Books/EditModal?id=" + data.record.id;` |
|||
|
|||
|
|||
## Create/Edit Book page |
|||
|
|||
Remove `Layout = null;` and add some custom style and javascript code to `CreateModal.cshtml` & `EditModal.cshtml`. |
|||
|
|||
```csharp |
|||
@section styles { |
|||
<style> |
|||
.abp-view-modal .modal { |
|||
position: static; |
|||
display: block; |
|||
opacity: inherit !important; |
|||
} |
|||
.abp-view-modal .modal.fade .modal-dialog { |
|||
transition: inherit !important; |
|||
transform: inherit !important;; |
|||
} |
|||
.abp-view-modal .modal-header .btn-close { |
|||
display: none; |
|||
} |
|||
</style> |
|||
} |
|||
@section scripts { |
|||
<script> |
|||
$(".abp-view-modal form").abpAjaxForm().on('abp-ajax-success', function () { |
|||
location.href = "/Books"; |
|||
}); |
|||
</script> |
|||
} |
|||
``` |
|||
|
|||
Add a `div` element with `abp-view-modal` class to wrap the `abp-dynamic-form`, Set size of `abp-modal` to `ExtraLarge` and remove the `AbpModalButtons.Cancel` button from `abp-modal-footer`. |
|||
|
|||
### CreateModal |
|||
```csharp |
|||
<div class="abp-view-modal"> |
|||
<abp-dynamic-form abp-model="Book" asp-page="/Books/CreateModal"> |
|||
<abp-modal static="true" size="ExtraLarge"> |
|||
<abp-modal-header title="@L["NewBook"].Value"></abp-modal-header> |
|||
<abp-modal-body> |
|||
<abp-form-content /> |
|||
</abp-modal-body> |
|||
<abp-modal-footer buttons="@(AbpModalButtons.Save)"></abp-modal-footer> |
|||
</abp-modal> |
|||
</abp-dynamic-form> |
|||
</div> |
|||
``` |
|||
|
|||
### EditModal |
|||
```csharp |
|||
<div class="abp-view-modal"> |
|||
<abp-dynamic-form abp-model="Book" asp-page="/Books/EditModal"> |
|||
<abp-modal size="ExtraLarge"> |
|||
<abp-modal-header title="@L["Update"].Value"></abp-modal-header> |
|||
<abp-modal-body> |
|||
<abp-form-content /> |
|||
</abp-modal-body> |
|||
<abp-modal-footer buttons="@(AbpModalButtons.Save)"></abp-modal-footer> |
|||
</abp-modal> |
|||
</abp-dynamic-form> |
|||
</div> |
|||
``` |
|||
|
|||
You can check this Git commit for details. |
|||
|
|||
https://github.com/abpframework/abp-samples/commit/f3014e0ec422cb2d8816d0e00dd6ab9cc1adfc21 |
|||
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
@ -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<AbpDistributedCacheOptions>(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<TCacheItem>` or `IDistributedCache<TCacheItem,TKey>` 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<AbpDistributedCacheOptions>`). 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<AbpDistributedLockOptions>(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<AbpStringEncryptionOptions>(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. |
|||
@ -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<OrderingServiceHttpApiHostModule>(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) |
|||
@ -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. |
|||
@ -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<TEntityCacheItem, TKey>` 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<Guid> |
|||
{ |
|||
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<Product, Guid>(); |
|||
``` |
|||
|
|||
Now you can inject the `IEntityCache<Product, Guid>` service wherever you need: |
|||
|
|||
```csharp |
|||
public class ProductAppService : ApplicationService, IProductAppService |
|||
{ |
|||
private readonly IEntityCache<Product, Guid> _productCache; |
|||
|
|||
public ProductAppService(IEntityCache<Product, Guid> productCache) |
|||
{ |
|||
_productCache = productCache; |
|||
} |
|||
|
|||
public async Task<ProductDto> GetAsync(Guid id) |
|||
{ |
|||
var product = await _productCache.GetAsync(id); |
|||
return ObjectMapper.Map<Product, ProductDto>(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<Guid> |
|||
{ |
|||
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<Product, ProductDto, Guid>(); |
|||
``` |
|||
|
|||
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<Product, ProductDto>(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Now, you can inject the `IEntityCache<ProductDto, Guid>` service wherever you want: |
|||
|
|||
```csharp |
|||
public class ProductAppService : ApplicationService, IProductAppService |
|||
{ |
|||
private readonly IEntityCache<ProductDto, Guid> _productCache; |
|||
|
|||
public ProductAppService(IEntityCache<ProductDto, Guid> productCache) |
|||
{ |
|||
_productCache = productCache; |
|||
} |
|||
|
|||
public async Task<ProductDto> 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<Product, ProductDto, Guid>( |
|||
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) |
|||
@ -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<AbpAuditingOptions>(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<AbpAspNetCoreMvcOptions>(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) |
|||
@ -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 |
|||
@ -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<string, object>` |
|||
|
|||
`ApplicationMenu` and `ApplicationMenuItem` classes' `CustomData` property type has been changed to `Dictionary<string, object>`. 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()); |
|||
``` |
|||
@ -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 |
|||
<PackageReference Include="Volo.Abp.OpenIddict.Domain.Shared" Version="6.0.*" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.IdentityServer.Domain.Shared" Version="6.0.*" /> |
|||
``` |
|||
|
|||
- 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 |
|||
<PackageReference Include="Volo.Abp.OpenIddict.Domain" Version="6.0.*" /> |
|||
<PackageReference Include="Volo.Abp.PermissionManagement.Domain.OpenIddict" Version="6.0.*" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.IdentityServer.Domain" Version="6.0.*" /> |
|||
<PackageReference Include="Volo.Abp.PermissionManagement.Domain.IdentityServer" Version="6.0.*" /> |
|||
``` |
|||
|
|||
- 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 |
|||
<PackageReference Include="Volo.Abp.OpenIddict.EntityFrameworkCore" Version="6.0.*" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.IdentityServer.EntityFrameworkCore" Version="6.0.*" /> |
|||
``` |
|||
|
|||
- 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 |
|||
<PackageReference Include="Volo.Abp.OpenIddict.MongoDB" Version="6.0.*" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.IdentityServer.MongoDB" Version="6.0.*" /> |
|||
``` |
|||
|
|||
- 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<AbpIdentityServerBuilderOptions>(options => |
|||
{ |
|||
options.AddDeveloperSigningCredential = false; |
|||
}); |
|||
|
|||
PreConfigure<IIdentityServerBuilder>(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) |
|||
@ -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. |
|||