@ -0,0 +1,214 @@ |
|||
# ABP Platform 10.4 RC Has Been Released |
|||
|
|||
We are happy to release [ABP](https://abp.io) version **10.4 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 v10.4! Thanks to you in advance. |
|||
|
|||
## Get Started with the 10.4 RC |
|||
|
|||
You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli). |
|||
|
|||
> The v10.4 RC versions of ABP Studio and the ABP CLI are still being tested and will be released shortly. |
|||
|
|||
By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI: |
|||
|
|||
 |
|||
|
|||
## Migration Guide |
|||
|
|||
There are no explicitly marked breaking changes in this version. However, there are still some important migration notes for specific scenarios. Please check the migration guide if you are upgrading from v10.3 or earlier: [ABP Version 10.4 Migration Guide](https://abp.io/docs/10.4/release-info/migration-guides/abp-10-4). |
|||
|
|||
## What's New with ABP v10.4? |
|||
|
|||
In this section, I will introduce some major features released in this version. |
|||
Here is a brief list of titles explained in the next sections: |
|||
|
|||
- URL-Based Localization |
|||
- Localization File Splitting |
|||
- Blazor UI: MudBlazor Support |
|||
- Identity: Single-Use Email/SMS 2FA Token Providers |
|||
- Account Pro: Passwordless Email Login |
|||
- AI Management: MCP Server Enhancements |
|||
- LeptonX: URL-Based Localization and Theme Improvements |
|||
- Dependency and Security Updates |
|||
|
|||
### URL-Based Localization |
|||
|
|||
ABP v10.4 introduces URL-based localization support. You can now embed the culture directly in the URL path, such as `/tr/products` or `/en/about`. |
|||
|
|||
This is especially useful for public websites, documentation sites, e-commerce applications, and any application that needs SEO-friendly and shareable localized URLs. Instead of relying only on query string, cookie, or browser language detection, the selected culture can be part of the URL itself. |
|||
|
|||
You can enable it with a single configuration: |
|||
|
|||
```csharp |
|||
Configure<AbpRequestLocalizationOptions>(options => |
|||
{ |
|||
options.UseRouteBasedCulture = true; |
|||
}); |
|||
``` |
|||
|
|||
When enabled, ABP automatically handles route registration, URL generation, menu links, and language switching for MVC/Razor Pages, Blazor, and Angular UIs. |
|||
|
|||
For Angular applications, route trees can be wrapped with `withOptionalRouteCulturePrefix` so the same route configuration can handle both `/identity/users` and `/en/identity/users`: |
|||
|
|||
```typescript |
|||
import { Routes } from '@angular/router'; |
|||
import { withOptionalRouteCulturePrefix } from '@abp/ng.core'; |
|||
|
|||
const appRoutesCore: Routes = [ |
|||
// ... your routes |
|||
]; |
|||
|
|||
export const appRoutes = withOptionalRouteCulturePrefix(appRoutesCore); |
|||
``` |
|||
|
|||
For Blazor applications, ABP built-in module pages already include culture-aware route variants. If you have your own Blazor pages, add culture route variants manually: |
|||
|
|||
```razor |
|||
@page "/Products" |
|||
@page "/{culture}/Products" |
|||
``` |
|||
|
|||
> See the [URL-Based Localization](https://abp.io/docs/10.4/framework/fundamentals/url-based-localization) documentation and [#25174](https://github.com/abpframework/abp/pull/25174) for details. |
|||
|
|||
### Localization File Splitting |
|||
|
|||
ABP localization resources can now use multiple JSON files for the same culture. This is useful for large modules or applications where keeping all localization texts in a single `en.json` file becomes difficult to maintain. |
|||
|
|||
For example, you can split a resource by feature: |
|||
|
|||
```text |
|||
Localization/ |
|||
+-- MyResource/ |
|||
+-- en.json |
|||
+-- en_Authors.json |
|||
+-- en_Books.json |
|||
+-- en_Users.json |
|||
``` |
|||
|
|||
ABP merges these files into the same localization dictionary. Files are sorted by name before merging, and if the same key exists in multiple files, the value from the last file wins. |
|||
|
|||
> See the [Localization](https://abp.io/docs/10.4/framework/fundamentals/localization) documentation and [#25227](https://github.com/abpframework/abp/pull/25227) for details. |
|||
|
|||
### Blazor UI: MudBlazor Support |
|||
|
|||
ABP v10.4 starts the [MudBlazor](https://mudblazor.com/) integration work for the Blazor UI stack. |
|||
|
|||
This release adds MudBlazor-based package infrastructure, template integration, and module/theme support needed to build ABP Blazor applications with MudBlazor. Blazorise and MudBlazor are now supported side by side, the LeptonX theme works with both UI libraries, and when creating a new Blazor project you can pick which UI library to use. |
|||
|
|||
This is a major UI foundation change, so we especially encourage Blazor users to try the RC and share feedback before the stable release. |
|||
|
|||
***Selecting the UI library when creating a new Blazor project in ABP Studio:*** |
|||
|
|||
 |
|||
|
|||
***MudBlazor-based application home page:*** |
|||
|
|||
 |
|||
|
|||
***MudBlazor-based Identity management page:*** |
|||
|
|||
 |
|||
|
|||
> See [#25235](https://github.com/abpframework/abp/pull/25235) for details. |
|||
|
|||
### Identity: Single-Use Email/SMS 2FA Token Providers |
|||
|
|||
ABP v10.4 improves the security model for email and SMS two-factor authentication codes. |
|||
|
|||
Email and phone verification codes now use ABP's single-use token providers. Generated codes are encrypted, stored with an absolute expiration time, and consumed after successful validation. Generating a new code invalidates the previous one. |
|||
|
|||
You can configure token lifetime and code length: |
|||
|
|||
```csharp |
|||
Configure<AbpEmailTwoFactorTokenProviderOptions>(options => |
|||
{ |
|||
options.TokenLifespan = TimeSpan.FromMinutes(5); |
|||
options.CodeLength = 8; |
|||
}); |
|||
|
|||
Configure<AbpPhoneNumberTwoFactorTokenProviderOptions>(options => |
|||
{ |
|||
options.TokenLifespan = TimeSpan.FromMinutes(2); |
|||
}); |
|||
``` |
|||
|
|||
The authenticator app provider is not affected and continues to use the standard TOTP approach. |
|||
|
|||
> See the [Two Factor Authentication](https://abp.io/docs/10.4/modules/identity/two-factor-authentication) documentation and [#25316](https://github.com/abpframework/abp/pull/25316) for details. |
|||
|
|||
### Account Pro: Passwordless Email Login |
|||
|
|||
ABP Commercial v10.4 RC introduces passwordless email login for the Account Pro module. |
|||
|
|||
Users can sign in by receiving an email login link and/or a one-time password (OTP), depending on the configured login type. Administrators can enable the feature, choose the login mode, and configure token lifetime from the account settings. |
|||
|
|||
 |
|||
|
|||
The feature is designed with security in mind: |
|||
|
|||
- Login links and OTPs are single-use. |
|||
- Resending a login email invalidates previous tokens. |
|||
- Token operations respect the current tenant context. |
|||
- Rate limiting helps protect against brute-force and email spam scenarios. |
|||
- Email enumeration behavior follows the existing account security setting. |
|||
|
|||
This feature is especially useful for applications that want a smoother sign-in experience without removing the tenant-aware and security-focused account flow of ABP. |
|||
|
|||
***"Login via email":*** |
|||
|
|||
 |
|||
|
|||
***Type the One-time Password (OTP) to login:*** |
|||
|
|||
 |
|||
|
|||
### AI Management: MCP Server Enhancements |
|||
|
|||
The [AI Management module](https://abp.io/docs/latest/modules/ai-management) continues to improve its MCP (Model Context Protocol) support. |
|||
|
|||
In this release, MCP server configuration has been enhanced for `stdio` transport scenarios and workspace relationships. This makes it easier to connect local or process-based MCP servers to AI workspaces and use their tools from the chat playground. |
|||
|
|||
### LeptonX: URL-Based Localization and Theme Improvements |
|||
|
|||
LeptonX has been updated to work with the new URL-based localization flow across UI types, including Angular language switching and culture-aware navigation. |
|||
|
|||
This release also includes several theme improvements and fixes, such as PathBase-safe menu links, improved custom select synchronization, sidebar menu re-binding after async rendering, and MudBlazor-related theme support. |
|||
|
|||
### Dependency and Security Updates |
|||
|
|||
ABP v10.4 RC includes several dependency updates and security-related package bumps: |
|||
|
|||
- OpenIddict upgraded to **7.5.0** |
|||
- MongoDB.Driver upgraded to **3.8.0** |
|||
- Microsoft/System package updates for CVE-2026-40372 |
|||
- `System.Security.Cryptography.Xml` upgraded to **10.0.6** |
|||
- `@abp/lodash` lodash dependency updated |
|||
|
|||
> Check [Package Version Changes](https://abp.io/docs/10.4/package-version-changes) document for all updates. |
|||
|
|||
### Other Improvements and Enhancements |
|||
|
|||
- **Virtual File System**: `ReplaceEmbeddedByPhysical` can now receive exclusion filters, which gives developers more control over included/excluded physical files during development ([#25284](https://github.com/abpframework/abp/pull/25284)). |
|||
- **Exception logging**: Complex objects in exception data are now serialized more clearly in logs ([#25267](https://github.com/abpframework/abp/pull/25267)). |
|||
- **Feature management**: Improved batch state checker performance and added `RequireFeaturesSimpleBatchStateChecker` ([#25276](https://github.com/abpframework/abp/pull/25276)). |
|||
- **RabbitMQ**: Fixed a potential hang while acquiring a closed channel after RabbitMQ restart ([#25311](https://github.com/abpframework/abp/pull/25311)). |
|||
- **Shared user accounts**: Improved shared-user lookup and two-factor authentication flows for shared user scenarios. |
|||
- **Account and SaaS modules**: Improved shared-user invitation and account-page flows in tenant user sharing scenarios. |
|||
|
|||
## Community News |
|||
|
|||
### New ABP Community Articles |
|||
|
|||
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: |
|||
|
|||
- [Stop Sprinkling [RequiresFeature] Everywhere — A Centralized Feature Gate for ABP.IO](https://abp.io/community/articles/stop-sprinkling-requiresfeature-everywhere-a-centralized-7znie818) by [Mohammad AlMohammad AlMahmoud](https://abp.io/community/members/Mohammad97Dev) |
|||
- [Top AI Coding Models in 2026: Which One Should Developers Actually Use?](https://abp.io/community/articles/top-ai-coding-models-in-2026-which-one-should-developers-use-rivh8x15) by [Alper Ebiçoğlu](https://abp.io/community/members/alper) |
|||
|
|||
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community. |
|||
|
|||
## Conclusion |
|||
|
|||
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/10.4/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v10.4 RC and provide feedback to help us release a more stable version. |
|||
|
|||
Thanks for being a part of this community! |
|||
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 457 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,167 @@ |
|||
# SEO-Friendly Localized URLs in ABP with a Single Line of Configuration |
|||
|
|||
ABP has always supported language switching via the `?culture=en` query string and the culture cookie. That works fine for most applications — but it has a limitation that shows up quickly once SEO or link-sharing matters. |
|||
|
|||
Consider a book-store app where users browse in their language: |
|||
|
|||
- A Spanish user shares a product link. The recipient opens it in English because the cookie on *their* machine says `en`. |
|||
- Search engines crawl the same URL in every language, making it impossible to create separate sitemaps per locale. |
|||
- A user shares a link like `/Books/Detail?id=42&culture=es`. When the server processes the request, it sets the culture cookie and then redirects to `/Books/Detail?id=42` — stripping the `?culture=` parameter. The shared link no longer carries the intended language. |
|||
|
|||
Embedding the culture in the URL path — `/es/books`, `/zh-Hans/about` — solves all three. Each language has its own stable URL, readable by humans and index-friendly for search engines. |
|||
|
|||
ABP supports this out of the box. You opt in with a single configuration property, and the framework takes care of routing, URL generation, menu links, and language switching automatically. |
|||
|
|||
## Enabling URL-Based Localization |
|||
|
|||
In your ABP module class, add: |
|||
|
|||
```csharp |
|||
Configure<AbpRequestLocalizationOptions>(options => |
|||
{ |
|||
options.UseRouteBasedCulture = true; |
|||
}); |
|||
``` |
|||
|
|||
That is the only change you need to make. |
|||
|
|||
## MVC / Razor Pages |
|||
|
|||
MVC and Razor Pages have the most complete support — everything works automatically. No code changes needed in your pages or controllers. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
## What Happens Automatically |
|||
|
|||
When you set `UseRouteBasedCulture = true`, ABP automatically: |
|||
|
|||
- Registers ASP.NET Core's built-in [`RouteDataRequestCultureProvider`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.localization.routing.routedatarequestcultureprovider) to detect culture from the URL path. |
|||
- Adds a `{culture}/{controller}/{action}` conventional route for MVC controllers, with a route constraint to prevent non-culture URL segments (like `/enterprise/products`) from matching. |
|||
- Adds `{culture}/...` route selectors to all Razor Pages at startup. |
|||
- Injects the current culture into all `Url.Page()` and `Url.Action()` calls, so generated URLs automatically include the culture prefix. |
|||
- Prepends the culture prefix to navigation menu item URLs. |
|||
|
|||
You do not need to configure these individually. |
|||
|
|||
## URL Generation Just Works |
|||
|
|||
In a Razor Page or view running under a culture-prefixed URL (say, `/zh-Hans/Books`), you do not need to pass a `culture` parameter anywhere: |
|||
|
|||
```cshtml |
|||
@Url.Page("/Books/Detail", new { id = book.Id }) |
|||
@* Generates: /zh-Hans/Books/Detail?id=42 *@ |
|||
|
|||
@Url.Action("About", "Home") |
|||
@* Generates: /zh-Hans/Home/About *@ |
|||
``` |
|||
|
|||
If you explicitly pass a different `culture` value, that takes precedence — so cross-language links are also straightforward: |
|||
|
|||
```cshtml |
|||
@Url.Page("/Books/Index", new { culture = "tr" }) |
|||
@* Generates: /tr/Books *@ |
|||
``` |
|||
|
|||
## Language Switching |
|||
|
|||
The built-in ABP language switcher already works with route-based culture. When a user switches language, the culture segment in the URL is automatically replaced: |
|||
|
|||
| Current URL | Switch to | Redirect to | |
|||
|---|---|---| |
|||
| `/tr/books` | `en` | `/en/books` | |
|||
| `/zh-Hans/about` | `en` | `/en/about` | |
|||
| `/tenant-a/zh-Hans/about` | `en` | `/tenant-a/en/about` | |
|||
|
|||
No theme changes, no language switcher changes — the existing UI component just works. |
|||
|
|||
## Blazor Support |
|||
|
|||
Blazor Server and Blazor WebAssembly (WebApp) both support URL-based localization. Culture detection and cookie persistence work automatically on the initial page load (SSR). Menu URLs and language switching also work automatically. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
ABP's built-in module pages (Identity, Settings, etc.) also work with URL-based localization out of the box: |
|||
|
|||
 |
|||
|
|||
### Manual step: Blazor component routes |
|||
|
|||
The only manual step for Blazor is adding `@page "/{culture}/..."` routes to your own pages. ASP.NET Core does not support automatically adding route selectors to Blazor components (unlike Razor Pages), so you must add them explicitly: |
|||
|
|||
```razor |
|||
@page "/" |
|||
@page "/{culture}" |
|||
|
|||
@code { |
|||
[Parameter] |
|||
public string? Culture { get; set; } |
|||
} |
|||
``` |
|||
|
|||
```razor |
|||
@page "/Products" |
|||
@page "/{culture}/Products" |
|||
|
|||
@code { |
|||
[Parameter] |
|||
public string? Culture { get; set; } |
|||
} |
|||
``` |
|||
|
|||
> **ABP's built-in module pages** (Identity, Tenant Management, Settings, Account, etc.) already ship with `@page "/{culture}/..."` route variants. You only need to add these routes to your own application pages. |
|||
|
|||
### Blazor WebApp (WASM) configuration |
|||
|
|||
The WASM client project does not need any `UseRouteBasedCulture` configuration. It reads the setting from the server automatically. |
|||
|
|||
```csharp |
|||
// Server project — the only place you need to configure |
|||
Configure<AbpRequestLocalizationOptions>(options => |
|||
{ |
|||
options.UseRouteBasedCulture = true; |
|||
}); |
|||
``` |
|||
|
|||
## Multi-Tenancy |
|||
|
|||
URL-based localization is fully compatible with ABP's multi-tenant routing. Language switching supports tenant-prefixed URLs, so `/tenant-a/zh-Hans/About` correctly switches to `/tenant-a/en/About` without any additional configuration. |
|||
|
|||
## UI Framework Support Overview |
|||
|
|||
| UI Framework | Route Registration | URL Generation | Menu URLs | Language Switch | Manual Work | |
|||
|---|---|---|---|---|---| |
|||
| **MVC / Razor Pages** | Automatic | Automatic | Automatic | Automatic | None | |
|||
| **Blazor Server** | Manual `@page` routes | N/A | Automatic | Automatic | Add `{culture}` route to pages | |
|||
| **Blazor WebApp (WASM)** | Manual `@page` routes | N/A | Automatic | Automatic | Add `{culture}` route to pages | |
|||
|
|||
## Running the Sample |
|||
|
|||
A runnable sample is available at [abp-samples/UrlBasedLocalization](https://github.com/abpframework/abp-samples/tree/master/UrlBasedLocalization), with three projects: |
|||
|
|||
| Project | UI Type | URL | Command | |
|||
|---|---|---|---| |
|||
| `BookStore.Mvc` | MVC / Razor Pages | `https://localhost:44335` | `dotnet run --project src/BookStore.Mvc` | |
|||
| `BookStore.Blazor.Server` | Blazor Server | `https://localhost:44336` | `dotnet run --project src/BookStore.Blazor.Server` | |
|||
| `BookStore.Blazor.WebApp` | Blazor WebApp (InteractiveAuto) | `https://localhost:44337` | `dotnet run --project src/BookStore.Blazor.WebApp` | |
|||
|
|||
Supported languages: English, Türkçe, Français, 简体中文. |
|||
|
|||
## Summary |
|||
|
|||
To add SEO-friendly localized URL paths to your ABP application: |
|||
|
|||
1. Set `options.UseRouteBasedCulture = true` in your module. |
|||
2. For **Blazor** projects, add `@page "/{culture}/..."` routes to your own pages. |
|||
|
|||
Everything else — route registration, URL generation, menu links, and language switching — is handled automatically. |
|||
|
|||
## References |
|||
|
|||
- [URL-Based Localization — ABP Documentation](https://abp.io/docs/latest/framework/fundamentals/url-based-localization) |
|||
- [Localization — ABP Documentation](https://abp.io/docs/latest/framework/fundamentals/localization) |
|||
- [abp-samples/UrlBasedLocalization — GitHub](https://github.com/abpframework/abp-samples/tree/master/UrlBasedLocalization) |
|||
- [Request Localization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization/select-language-culture) |
|||
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 93 KiB |
@ -0,0 +1,254 @@ |
|||
## Introduction |
|||
|
|||
AI coding tools went from “cool autocomplete” to “basically your junior dev (who never sleeps)” in just a couple of years. |
|||
|
|||
In 2026, the landscape is **crowded, competitive, and honestly a bit confusing**. Every model claims to be the best at coding—but depending on what you actually *do* (APIs, frontend, DevOps, debugging), the “best” can change fast. |
|||
|
|||
So instead of hype, let’s break down the **top AI coding models in 2026**, ranked by: |
|||
|
|||
* Real-world dev usefulness |
|||
* Code quality & correctness |
|||
* Context handling |
|||
* Tooling ecosystem |
|||
|
|||
We'll check the AI models against these topics: |
|||
|
|||
 |
|||
|
|||
--- |
|||
|
|||
## 🏆 1. GPT-5.4 (OpenAI) — The All-Round Beast |
|||
|
|||
Let’s not dance around it—**GPT-5.4 is still the most versatile coding model right now.** |
|||
|
|||
### Why it’s #1 |
|||
|
|||
* Extremely strong across **all languages** |
|||
* Handles **large codebases** without losing context |
|||
* Excellent at: |
|||
|
|||
* Refactoring |
|||
* Architecture suggestions |
|||
* Debugging complex issues |
|||
|
|||
### Where it shines |
|||
|
|||
* Full-stack development |
|||
* API design |
|||
* Writing clean, production-ready code |
|||
|
|||
### Where it struggles |
|||
|
|||
* Occasionally over-engineers solutions |
|||
* Can be slower than lightweight models |
|||
|
|||
### As a result; |
|||
|
|||
If you want a **default “just works” coding AI**, this is it. |
|||
|
|||
--- |
|||
|
|||
## 🥈 2. Claude 4.7 (Anthropic) — The Clean Code Specialist |
|||
|
|||
Claude 4.7 has built a reputation for writing code that feels like it came from a senior engineer who drinks too much coffee but cares deeply about readability. |
|||
|
|||
### Strengths |
|||
|
|||
* Beautiful, readable code |
|||
* Strong reasoning for: |
|||
|
|||
* Refactoring |
|||
* Code reviews |
|||
* Documentation |
|||
|
|||
### Killer feature |
|||
|
|||
* Massive context window → great for: |
|||
|
|||
* Large repositories |
|||
* Long discussions |
|||
* System design |
|||
|
|||
### Weak spots |
|||
|
|||
* Slightly less aggressive in solving edge-case bugs |
|||
* Sometimes too “safe” in decisions |
|||
|
|||
### As a result; |
|||
|
|||
Perfect if you care about **maintainability over raw speed**. |
|||
|
|||
--- |
|||
|
|||
## 🥉 3. Gemini 3.1 (Google) — The Multimodal Powerhouse |
|||
|
|||
Gemini 3.1 is where things get interesting. |
|||
|
|||
This isn’t just a coding model—it’s a **multi-input problem solver**. |
|||
|
|||
### What makes it different |
|||
|
|||
* Understands: |
|||
|
|||
* Code |
|||
* Screenshots |
|||
* Diagrams |
|||
* Logs |
|||
|
|||
### Where it dominates |
|||
|
|||
* Debugging UI issues from screenshots |
|||
* DevOps + cloud workflows |
|||
* Cross-referencing documentation |
|||
|
|||
### Downsides |
|||
|
|||
* Code style can be inconsistent |
|||
* Sometimes less deterministic than GPT-5 |
|||
|
|||
### As a result; |
|||
|
|||
If your workflow includes **visual debugging or cloud-heavy systems**, this is insanely useful. |
|||
|
|||
--- |
|||
|
|||
## ⚡ 4. Mistral Code (Open Models) — The Speed King |
|||
|
|||
Mistral AI’s coding models are gaining serious attention. |
|||
|
|||
### Why devs love it |
|||
|
|||
* Fast |
|||
* Cheap (or free if self-hosted) |
|||
* Great for: |
|||
|
|||
* Autocomplete |
|||
* Small functions |
|||
* Local development |
|||
|
|||
### Trade-offs |
|||
|
|||
* Not as strong in deep reasoning |
|||
* Limited compared to closed models |
|||
|
|||
### As a result; |
|||
|
|||
Best choice for: |
|||
|
|||
* Privacy-sensitive environments |
|||
* Offline/local setups |
|||
* Lightweight coding tasks |
|||
|
|||
--- |
|||
|
|||
## 🧠 5. Code Llama 4 — The Open-Source Veteran |
|||
|
|||
Code Llama 4 is still very relevant, especially in enterprise setups. |
|||
|
|||
### Strengths |
|||
|
|||
* Fully open-source |
|||
* Customizable & fine-tunable |
|||
* Good baseline performance |
|||
|
|||
### Weaknesses |
|||
|
|||
* Behind top-tier models in reasoning |
|||
* Needs tuning for best results |
|||
|
|||
### As a result; |
|||
|
|||
If your company says “no cloud AI,” this is your friend. |
|||
|
|||
--- |
|||
|
|||
## 📊 Comparison Table Between AI Models |
|||
|
|||
| Model | Best For | Weakness | |
|||
| ------------ | ------------------------ | --------------------- | |
|||
| GPT-5.4 | Everything | Slightly slower | |
|||
| Claude 4.7 | Clean, maintainable code | Less aggressive fixes | |
|||
| Gemini 3.1 | Multimodal workflows | Inconsistent style | |
|||
| Mistral Code | Speed & local usage | Shallow reasoning | |
|||
| Code Llama 4 | Open-source flexibility | Needs tuning | |
|||
|
|||
Image Prompt: |
|||
A sleek table-style infographic comparing AI models with icons, performance bars, and labels like “Best for speed”, “Best for reasoning”. |
|||
|
|||
--- |
|||
|
|||
## 🤔 When to Use What (Real Scenarios) |
|||
|
|||
### Use GPT-5.4 if: |
|||
|
|||
* You’re building a full product |
|||
* You need architecture + implementation |
|||
* You want fewer “AI mistakes” |
|||
|
|||
--- |
|||
|
|||
### Use Claude 4.7 if: |
|||
|
|||
* You’re reviewing code |
|||
* You care about readability |
|||
* You’re working in a team |
|||
|
|||
--- |
|||
|
|||
### Use Gemini 3.1 if: |
|||
|
|||
* You debug using screenshots/logs |
|||
* You work with cloud infrastructure |
|||
* You want multimodal workflows |
|||
|
|||
--- |
|||
|
|||
### Use Mistral / Code Llama if: |
|||
|
|||
* You need local/private AI |
|||
* You want low cost |
|||
* You’re okay trading power for control |
|||
|
|||
--- |
|||
|
|||
## 🔌 Where ABP Framework Fits In |
|||
|
|||
If you're working with **ASP.NET Core and the ABP Framework**, these models can seriously boost productivity: |
|||
|
|||
* GPT-5.4 → Generate **application services, DTOs, and modules** |
|||
* Claude → Clean up **domain layer logic** |
|||
* Gemini → Help debug **UI + backend integration issues** |
|||
|
|||
The sweet spot? |
|||
|
|||
👉 Use AI to scaffold ABP layers, then refine manually. |
|||
That keeps your architecture clean while still saving hours. |
|||
|
|||
--- |
|||
|
|||
## 🚨 Reality Check |
|||
|
|||
AI coding models in 2026 are powerful—but: |
|||
|
|||
* They still hallucinate edge cases |
|||
* They don’t fully understand your business logic |
|||
* They can fix somewhere, break another |
|||
* They can not fix a bug even after you write 10 different prompts |
|||
|
|||
So yeah—**don’t ship blind**. |
|||
|
|||
Treat them like: |
|||
|
|||
> A fast junior dev… who needs code review. |
|||
|
|||
--- |
|||
|
|||
## TL;DR |
|||
|
|||
👉 There’s no single “winner”—just the best tool for your workflow. |
|||
|
|||
 |
|||
|
|||
--- |
|||
|
|||
If you're experimenting with these models in real projects (especially with ABP), it's worth trying **multiple models side-by-side**. The differences become obvious *fast*. |
|||
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 660 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 379 KiB |
@ -0,0 +1,213 @@ |
|||
# ABP 10.4.2 Expands Blazor UI Options with MudBlazor Support |
|||
|
|||
With ABP 10.4.2, new Blazor projects can now use **MudBlazor** (Material Design) as an alternative to the long-standing default, **Blazorise** (Bootstrap 5). Framework, themes (LeptonX / LeptonX Lite / Basic), modules, solution templates, ABP Studio, and ABP Suite all support both libraries side by side. The 10.4.2 packages are live on nuget.org. |
|||
|
|||
## Why add another Blazor UI library? |
|||
|
|||
Blazorise has been ABP's default Blazor UI library for years and **remains the default and is fully supported** — existing Blazorise projects can keep moving at their own pace, and upgrading to 10.4 does not change anything for them. |
|||
|
|||
We added MudBlazor because one Blazor UI choice cannot fit every team: |
|||
|
|||
- **Design language** — Bootstrap and Material Design serve different audiences, and forcing a single choice does not fit every team |
|||
- **Open-source preference** — MudBlazor is MIT-licensed, which works well for teams that want an open-source frontend component stack without extra component-library licensing or compliance overhead |
|||
- **Ecosystem fit** — Material Design third-party components (charts, rich text editors, data visualization, and so on) tend to integrate more naturally with a MudBlazor project |
|||
|
|||
For new projects you can start with MudBlazor right away. Existing Blazorise projects do not need to be rewritten just to switch UI libraries. |
|||
|
|||
### Who should consider MudBlazor? |
|||
|
|||
- Teams that want the frontend component stack **fully open source** with no licensing to manage (individual developers, open-source community projects, education / learning settings) |
|||
- Organizations with internal **third-party dependency or supply-chain compliance** requirements that prefer MIT-licensed components |
|||
- New Blazor projects that want to start with **Material Design** |
|||
- Teams already comfortable with the **MudBlazor ecosystem** (charts, rich text, rich UI components) |
|||
|
|||
## What the MudBlazor option covers |
|||
|
|||
### Framework core |
|||
|
|||
`Volo.Abp.MudBlazorUI` provides the MudBlazor implementation of ABP's UI service abstractions, so code written against `IUiMessageService` / `IUiNotificationService` / `IUiPageProgressService` runs unchanged in a MudBlazor project. Key building blocks: |
|||
|
|||
- `MudBlazorUiMessageService` — `Info` / `Success` / `Warn` / `Error` / `Confirm` rendered through `MudDialog` |
|||
- `MudBlazorUiNotificationService` — toast notifications via `MudSnackbar` |
|||
- `MudBlazorUiPageProgressService` — top progress bar via `MudProgressLinear` |
|||
- `AbpMudCrudPageBase<...>` — the MudBlazor counterpart to Blazorise's `AbpCrudPageBase` |
|||
- `AbpMudExtensibleDataGrid<TItem>` — a `MudDataGrid` wrapper integrated with Object Extension and time-zone conversion |
|||
- `UiMessageAlert` / `UiNotificationAlert` / `PageAlert` — page-level alert and notification containers |
|||
|
|||
Theming is split across three hosts — Blazor Server, WebAssembly, and MauiBlazor — each shipped with matching bundling contributors and modules that wire MudBlazor's JS and CSS into the ABP bundle system. |
|||
|
|||
### Three themes |
|||
|
|||
- **LeptonX MudBlazor** |
|||
- **LeptonX Lite MudBlazor** |
|||
- **Basic Theme MudBlazor** |
|||
|
|||
Each theme's layout adopts MudBlazor components such as `MudAppBar`, `MudDrawer`, `MudNavLink`, and `MudMenu`, while keeping the theme's original color palette, dim / light / system modes, and RTL support. |
|||
|
|||
 |
|||
*LeptonX rendered with MudBlazor* |
|||
|
|||
 |
|||
*LeptonX Lite rendered with MudBlazor* |
|||
|
|||
 |
|||
*Basic Theme rendered with MudBlazor* |
|||
|
|||
The LeptonX themes reuse the same `lpx-*` CSS classes across both UI libraries, so the overall information architecture, page layout, and theme experience stay consistent with the Blazorise version. Individual controls follow each UI library's own conventions. |
|||
|
|||
 |
|||
*The same LeptonX theme — MudBlazor on the left, Blazorise on the right* |
|||
|
|||
### Module coverage |
|||
|
|||
Open-source modules in `abpframework/abp` that ship with a MudBlazor implementation: |
|||
|
|||
- **Account** |
|||
- **Identity** — Users / Roles / OUs / ClaimTypes |
|||
- **Permission Management** — parent/child permissions with `MudTreeView` and tri-state `MudCheckBox` |
|||
- **Setting Management** — grouped settings with `MudTabs` (including theme switching) |
|||
- **Tenant Management** |
|||
- **Feature Management** |
|||
|
|||
Additional MudBlazor implementations available on the Pro side, for example: |
|||
|
|||
- **Identity Pro** — extra management around Sessions, SecurityLogs, and more |
|||
- **OpenIddict Pro** — Application / Scope management |
|||
- **Saas** — Tenant / Edition management with a connection-string dialog |
|||
- **Audit Logging** — `MudDataGrid` with a detail `MudDialog` |
|||
- **Language Management** / **Text Template Management** |
|||
- **File Management** / **Chat** / **CMS Kit Pro** |
|||
- **AI Management** / **GDPR** / **Payment**, and more |
|||
|
|||
 |
|||
*Identity user management built on `AbpMudExtensibleDataGrid`* |
|||
|
|||
 |
|||
*Permission Management uses `MudTreeView` and tri-state `MudCheckBox` for parent/child permissions* |
|||
|
|||
 |
|||
*Saas module: tenant list with a "New tenant" dialog that includes connection-string editing* |
|||
|
|||
### Component mapping at a glance |
|||
|
|||
If you already know Blazorise, here are the most common mappings: |
|||
|
|||
| Blazorise | MudBlazor | |
|||
|-----------|-----------| |
|||
| `TextEdit @bind-Text` | `MudTextField @bind-Value` | |
|||
| `Select / SelectItem` | `MudSelect / MudSelectItem` | |
|||
| `DataGrid` | `MudDataGrid` (wrapped by ABP as `AbpMudExtensibleDataGrid`) | |
|||
| `Modal Show()/Hide()` | `MudDialog ShowAsync()/CloseAsync()` | |
|||
| `Validations` | `MudForm` + built-in validation | |
|||
| `Row / Column ColumnSize.Is6` | `MudGrid / MudItem xs="12" sm="6"` | |
|||
| Bootstrap Icons `bi-*` | `Icons.Material.Filled.*` | |
|||
|
|||
A full mapping table with razor examples lives in the [ABP Blazor UI documentation](https://abp.io/docs/latest/framework/ui/blazor). |
|||
|
|||
### Supported Blazor project types |
|||
|
|||
ABP's MudBlazor support covers the Blazor project types you can create and run directly: |
|||
|
|||
- **Blazor Server** (`-u blazor-server`) |
|||
- **Blazor WebAssembly** (`-u blazor`) |
|||
- **Blazor WebApp** (`-u blazor-webapp`, including InteractiveAuto) |
|||
|
|||
### ABP Suite |
|||
|
|||
ABP Suite detects the solution's UI library and generates the matching CRUD page automatically: |
|||
|
|||
```csharp |
|||
public partial class Books : AbpMudCrudPageBase<IBookAppService, BookDto, Guid, GetBookListInput, CreateUpdateBookDto> |
|||
{ |
|||
private MudDialog _createDialog; |
|||
private MudForm _createFormRef; |
|||
} |
|||
``` |
|||
|
|||
The razor templates also split by UI library — Blazorise uses `<DataGrid>` + `<Modal>` + `<Validations>`, MudBlazor uses `<MudDataGrid>` + `<MudDialog>` + `<MudForm>`. |
|||
|
|||
## Choosing between Blazorise and MudBlazor |
|||
|
|||
Both UI libraries are production-ready and neither is strictly better. Common factors: |
|||
|
|||
- **Familiarity** — teams comfortable with Bootstrap tend to stay on Blazorise; teams comfortable with Material Design pick MudBlazor |
|||
- **Design system** — Bootstrap-style products lean toward Blazorise, Material Design products lean toward MudBlazor |
|||
- **Ecosystem** — existing Bootstrap component libraries or design assets fit Blazorise; Material Design third-party components fit MudBlazor more naturally |
|||
- **Existing projects** — keep maintaining live Blazorise projects as they are; if you want to try MudBlazor, start a new project with it |
|||
- **Licensing** — the two UI libraries have different license terms, so check each library's official license page before making a choice ([Blazorise](https://blazorise.com/license) / [MudBlazor](https://github.com/MudBlazor/MudBlazor/blob/dev/LICENSE)) |
|||
|
|||
Do not mix the two libraries within a single project — the choice is per solution, not per file. |
|||
|
|||
## Creating a MudBlazor project in ABP Studio |
|||
|
|||
### ABP Studio (recommended) |
|||
|
|||
Open ABP Studio → **New Solution** → pick a template → in the UI configuration step, select **Blazor UI library = MudBlazor**. Everything else works the same as a Blazorise project. After Build & Run you land on a MudBlazor-styled application. |
|||
|
|||
 |
|||
*New Solution wizard: pick MudBlazor for the Blazor UI library* |
|||
|
|||
 |
|||
*Studio Build & Run brings up a MudBlazor + LeptonX dashboard in the embedded browser* |
|||
|
|||
### CLI |
|||
|
|||
```bash |
|||
# Blazorise (default; --blazor-ui-library can be omitted) |
|||
abp new MyApp -u blazor |
|||
|
|||
# MudBlazor |
|||
abp new MyApp -u blazor --blazor-ui-library mudblazor |
|||
|
|||
# Tiered + WebApp + LeptonX + MudBlazor |
|||
abp new MyApp -t app --tiered -u blazor-webapp --blazor-ui-library mudblazor --theme leptonx |
|||
|
|||
# Microservice + MudBlazor + Blazor Server |
|||
abp new MyApp -t microservice -u blazor-server --blazor-ui-library mudblazor |
|||
|
|||
# Reusable Module + MudBlazor |
|||
abp new My.Module -t module -u blazor --blazor-ui-library mudblazor |
|||
``` |
|||
|
|||
Run `abp new --help` for the full option list. |
|||
|
|||
Suite-generated MudBlazor CRUD pages are covered in the **ABP Suite** section above. |
|||
|
|||
--- |
|||
|
|||
## Try it out |
|||
|
|||
```bash |
|||
abp new MyMudApp -u blazor-server --blazor-ui-library mudblazor --theme leptonx |
|||
``` |
|||
|
|||
Documentation: |
|||
|
|||
- [Forms & Validation (MudBlazor)](https://abp.io/docs/latest/framework/ui/blazor/forms-validation?BlazorUI=MudBlazor) |
|||
- [LeptonX with MudBlazor](https://abp.io/docs/latest/ui-themes/lepton-x/blazor) |
|||
- [Basic Theme MudBlazor variant](https://abp.io/docs/latest/framework/ui/blazor/basic-theme) |
|||
- [Page Header (MudBlazor)](https://abp.io/docs/latest/framework/ui/blazor/page-header) |
|||
|
|||
## FAQ |
|||
|
|||
**I'm already using Blazorise — will upgrading to 10.4 / 10.4.2 break my project?** |
|||
No. Blazorise stays the default, and package paths, type names, and namespaces are fully compatible. Follow the standard ABP upgrade flow. |
|||
|
|||
**Can I use Blazorise and MudBlazor in the same project?** |
|||
We don't recommend it. The UI library is a project-level choice — themes, bundling, and module dependencies all switch with it. Mixing both within a single solution leads to bundle conflicts, duplicated layouts, and similar issues. |
|||
|
|||
**What about my custom razor pages?** |
|||
Your custom Razor pages are tied to the UI library they were built with, so switching libraries means rewriting those pages using the component mapping above. Template-generated pages and module-provided pages don't need to be touched. |
|||
|
|||
## Wrapping up |
|||
|
|||
MudBlazor is now a first-class Blazor UI library in ABP. With 10.4.2 released, every related package, theme, template, Studio integration, and Suite generator is in place — you can try it out with a single `abp new` command. |
|||
|
|||
If you hit a bug, have a suggestion, or want a particular module's MudBlazor UX prioritized, let us know via [GitHub Issues](https://github.com/abpframework/abp/issues) or [abp.io support](https://abp.io/support). |
|||
|
|||
## References |
|||
|
|||
- [MudBlazor official site](https://mudblazor.com) |
|||
- [ABP Blazor UI documentation](https://abp.io/docs/latest/framework/ui/blazor) |
|||
- [ABP LeptonX theme](https://abp.io/themes/leptonx) |
|||
- [ABP Studio download](https://abp.io/studio) |
|||
@ -0,0 +1,173 @@ |
|||
````json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how to use ABP's URL-based localization to embed culture in the URL path, enabling SEO-friendly and shareable localized URLs." |
|||
} |
|||
```` |
|||
|
|||
# URL-Based Localization |
|||
|
|||
ABP supports embedding the current culture directly in the URL path, for example `/tr/products` or `/en/about`. This approach is widely used by documentation sites, e-commerce platforms, and any site that needs SEO-friendly, shareable localized URLs. |
|||
|
|||
By default, ABP detects language from QueryString (`?culture=tr`), Cookie, and `Accept-Language` header. URL path detection is **opt-in** and fully backward-compatible. |
|||
|
|||
## Enabling URL-Based Localization |
|||
|
|||
Configure the `AbpRequestLocalizationOptions` in your [module class](../architecture/modularity/basics.md): |
|||
|
|||
````csharp |
|||
Configure<AbpRequestLocalizationOptions>(options => |
|||
{ |
|||
options.UseRouteBasedCulture = true; |
|||
}); |
|||
```` |
|||
|
|||
That's all you need. The framework automatically handles the rest. |
|||
|
|||
## What Happens Automatically |
|||
|
|||
When you set `UseRouteBasedCulture` to `true`, ABP automatically registers the following: |
|||
|
|||
* **`RouteDataRequestCultureProvider`** — A built-in ASP.NET Core provider that reads `{culture}` from route data. ABP inserts it after `QueryStringRequestCultureProvider` and before `CookieRequestCultureProvider`. |
|||
* **`{culture}/{controller}/{action}` route** — A conventional route for MVC controllers. The `{culture}` parameter uses a custom route constraint (`AbpCultureRouteConstraint`) that only matches culture values configured in `AbpLocalizationOptions.Languages`, so URLs like `/enterprise/products` are not mistaken for culture-prefixed routes. |
|||
* **`AbpCultureRoutePagesConvention`** — An `IPageRouteModelConvention` that adds `{culture}/...` route selectors to all Razor Pages. |
|||
* **`AbpCultureRouteUrlHelperFactory`** — Replaces the default `IUrlHelperFactory` to auto-inject culture into `Url.Page()` and `Url.Action()` calls. |
|||
* **`AbpCultureMenuItemUrlProvider`** — Prepends the culture prefix to navigation menu item URLs (MVC / Blazor Server). |
|||
* **`AbpWasmCultureMenuItemUrlProvider`** — Prepends the culture prefix to menu item URLs in Blazor WebAssembly (reads the `UseRouteBasedCulture` flag from `/api/abp/application-configuration`). |
|||
|
|||
You do not need to configure these individually. |
|||
|
|||
## URL Generation |
|||
|
|||
When a request has a `{culture}` route value, all URL generation methods automatically include the culture prefix: |
|||
|
|||
````csharp |
|||
// In a Razor Page — culture is auto-injected, no manual parameter needed |
|||
@Url.Page("/About") // Generates: /zh-Hans/About |
|||
@Url.Action("About", "Home") // Generates: /zh-Hans/Home/About |
|||
```` |
|||
|
|||
Menu items registered via `IMenuContributor` also automatically get the culture prefix. No changes are needed in your menu contributors or theme. |
|||
|
|||
## Language Switching |
|||
|
|||
ABP's built-in language switcher (the `/Abp/Languages/Switch` action) automatically replaces the culture segment in the `returnUrl`. The controller reads the culture from the request cookie to identify the current page culture and replaces it with the new one: |
|||
|
|||
| Before switching | After switching to English | |
|||
|---|---| |
|||
| `/tr/products` | `/en/products` | |
|||
| `/tenant-a/zh-Hans/about` | `/tenant-a/en/about` | |
|||
| `/home?culture=tr&ui-culture=tr` | `/home?culture=en&ui-culture=en` | |
|||
| `/about` (no prefix) | `/about` (unchanged) | |
|||
|
|||
No changes are needed in any theme or language switcher component. |
|||
|
|||
## MVC / Razor Pages |
|||
|
|||
MVC and Razor Pages have the most complete support. Everything works automatically when `UseRouteBasedCulture = true` — route registration, URL generation, menu links, and language switching. **No code changes are needed in your pages or controllers.** |
|||
|
|||
## Blazor Server |
|||
|
|||
Blazor Server uses SignalR (WebSocket) for the interactive circuit. The HTTP middleware pipeline only runs on the **initial page load** — subsequent interactions happen over the WebSocket connection. ABP handles this by persisting the detected URL culture to a **Cookie** on the first request, so the entire Blazor circuit uses the correct language. |
|||
|
|||
Culture detection, cookie persistence, menu URLs, and language switching all work automatically. No additional configuration is needed beyond the `UseRouteBasedCulture` option. |
|||
|
|||
### What requires manual changes |
|||
|
|||
**Blazor component routes**: ASP.NET Core does not provide an `IPageRouteModelConvention` equivalent for Blazor components. You must manually add the `{culture}` route to each page: |
|||
|
|||
````razor |
|||
@page "/" |
|||
@page "/{culture}" |
|||
|
|||
@code { |
|||
[Parameter] |
|||
public string? Culture { get; set; } |
|||
} |
|||
```` |
|||
|
|||
````razor |
|||
@page "/About" |
|||
@page "/{culture}/About" |
|||
|
|||
@code { |
|||
[Parameter] |
|||
public string? Culture { get; set; } |
|||
} |
|||
```` |
|||
|
|||
> This applies to your own application pages. ABP built-in module pages (Identity, Tenant Management, Settings, Account, etc.) already include `@page "/{culture}/..."` routes out of the box — you do not need to add them manually. |
|||
|
|||
## Blazor WebAssembly (WebApp) |
|||
|
|||
Blazor WebAssembly (WASM) runs in the browser. On the **first page load**, the server renders the page via SSR, and the culture is detected from the URL. After WASM downloads, subsequent renders run in the browser. The WASM app fetches `/api/abp/application-configuration` from the server to get the current culture, so the culture stays consistent. |
|||
|
|||
Culture detection, cookie persistence, menu URLs, and language switching all work automatically. The WASM client reads the `UseRouteBasedCulture` flag from the server via `/api/abp/application-configuration`, so no client-side configuration is needed. |
|||
|
|||
### What requires manual changes |
|||
|
|||
Same as Blazor Server — you must manually add `@page "/{culture}/..."` routes to your Blazor pages. |
|||
|
|||
## Angular |
|||
|
|||
The [ABP Angular UI](../ui/angular/quick-start.md) runs in the browser. The server still applies `UseRouteBasedCulture`; the client reads **`localization.useRouteBasedCulture`** from `/api/abp/application-configuration` (same payload as other UI types). There is no separate Angular setting. |
|||
|
|||
### Routing |
|||
|
|||
Angular does not add a culture segment to your route config automatically. Use **`withOptionalRouteCulturePrefix`** from **`@abp/ng.core`** so one route tree matches both **`/identity/users`** and **`/en/identity/users`** (the first path segment is matched only when it looks like a culture code, e.g. `en`, `tr`, `zh-Hans`). |
|||
|
|||
````typescript |
|||
import { Routes } from '@angular/router'; |
|||
import { withOptionalRouteCulturePrefix } from '@abp/ng.core'; |
|||
|
|||
const appRoutesCore: Routes = [ |
|||
// ... your routes (path: '', 'account', 'identity', lazy children, etc.) |
|||
]; |
|||
|
|||
export const appRoutes = withOptionalRouteCulturePrefix(appRoutesCore); |
|||
```` |
|||
|
|||
 |
|||
|
|||
### URL → session language |
|||
|
|||
When **`useRouteBasedCulture`** is **true**, **`RouteBasedCultureService`** (from `@abp/ng.core`) keeps the session language aligned with the first URL segment after navigation. This runs during application bootstrap and on each **`NavigationEnd`**. |
|||
|
|||
### Menu links, breadcrumbs, and `routerLink` |
|||
|
|||
Menu paths from **`RoutesService`** are usually **without** a culture prefix (`/identity/users`). Use the **`abpRouteCultureUrl`** pipe on **`routerLink`** (or **`RouteBasedCultureUrlService.prefixPathWithCulture`**) so links navigate to **`/en/identity/users`** when route-based culture is enabled. The **Basic** theme navigation and **Theme Shared** breadcrumb links follow this pattern. |
|||
|
|||
 |
|||
|
|||
### Language switcher (toolbar) |
|||
|
|||
If the user selects a language in the UI, call **`RouteBasedCultureUrlService.applyLanguageSelection(cultureName)`** (or **`navigateToUrlWithCulture`**) instead of only updating the session language. That rewrites the current URL’s culture segment (or prepends it) so the address bar and session stay consistent; **`RouteBasedCultureService`** then picks up the culture from the URL after navigation. |
|||
|
|||
### Active menu, breadcrumbs, and route matching |
|||
|
|||
The browser URL may be **`/en/identity/users`** while menu items and **`RoutesService`** paths stay **`/identity/users`**. For comparisons (active state, **`findRoute`**, permission guard, dynamic layout), normalize the current URL with **`RouteBasedCultureUrlService.normalizeForMenuMatch`** (or **`stripCulturePrefixIfEnabled`**) or use **`getRoutePathForMatching`** where **`getRoutePath`** was used. |
|||
|
|||
### Configuration refresh |
|||
|
|||
**`RouteBasedCultureUrlService`** refreshes its cached **`useRouteBasedCulture`** and **languages** when application configuration is updated (for example after **`refreshAppState`**), so hot paths do not query configuration on every change detection cycle. |
|||
|
|||
## Multi-Tenancy Compatibility |
|||
|
|||
URL-based localization is fully compatible with [multi-tenancy URL routing](../architecture/multi-tenancy/index.md). The culture route is registered as a conventional route `{culture}/{controller}/{action}`. If your application uses tenant routing (e.g., `/{tenant}/...`), the tenant middleware strips the tenant segment before routing, and the culture segment is handled separately. |
|||
|
|||
Language switching also supports tenant-prefixed URLs. For example, `/tenant-a/zh-Hans/About` correctly switches to `/tenant-a/en/About`. |
|||
|
|||
## API Routes |
|||
|
|||
Routes like `/api/products` have no `{culture}` segment, so `RouteDataRequestCultureProvider` returns `null` and falls through to the next provider (Cookie → `Accept-Language` → default). API routes are completely unaffected. |
|||
|
|||
## Culture Detection Priority |
|||
|
|||
ASP.NET Core has a built-in [`RouteDataRequestCultureProvider`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.localization.routing.routedatarequestcultureprovider) (in `Microsoft.AspNetCore.Localization.Routing`) that reads culture from route data, but it is not included in the default provider list. When `UseRouteBasedCulture` is enabled, ABP inserts it after `QueryStringRequestCultureProvider` and before `CookieRequestCultureProvider`. The resulting provider order is: |
|||
|
|||
1. `QueryStringRequestCultureProvider` (ASP.NET Core default — useful for debugging and testing) |
|||
2. `RouteDataRequestCultureProvider` (URL path — inserted by ABP when enabled) |
|||
3. `CookieRequestCultureProvider` (ASP.NET Core default) |
|||
4. `AcceptLanguageHeaderRequestCultureProvider` (ASP.NET Core default) |
|||
|
|||
If a URL contains an invalid culture code (e.g. `/xyz1234/page`), `RequestLocalizationMiddleware` ignores it and falls through to the next provider. No error is thrown. |
|||
@ -0,0 +1,164 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Configure cross-application URLs in ABP with AppUrlOptions and IAppUrlProvider, including multi-tenant subdomain templates and redirect URL validation." |
|||
} |
|||
``` |
|||
|
|||
# Application URLs |
|||
|
|||
ABP provides the `AppUrlOptions` options class and the `IAppUrlProvider` service to centrally configure and resolve URLs that point to **other applications** in your solution (for example, an MVC/Razor Pages UI, an Auth Server, an HTTP API host, etc.). They are typically used when code in one application needs to build a link that targets another — like the Account module putting a **password reset link** into an email. |
|||
|
|||
* Defines `AppUrlOptions` to register the **root URL** and named relative URLs of each application. |
|||
* Provides `IAppUrlProvider` to **resolve** those URLs at runtime, with optional **tenant-aware** placeholder substitution. |
|||
* Supports **subdomain-style templates** (e.g. `https://{0}.example.com`) that produce per-tenant URLs without extra code. |
|||
* Maintains a `RedirectAllowedUrls` list used by `IAppUrlProvider.IsRedirectAllowedUrlAsync` to validate redirect targets. |
|||
|
|||
> `AppUrlOptions` is defined in the `Volo.Abp.UI.Navigation` package, which comes pre-installed with the [application startup template](../../solution-templates/layered-web-application). |
|||
|
|||
## Configuring Application URLs |
|||
|
|||
`AppUrlOptions` exposes a dictionary of **applications**, each with a `RootUrl` and a set of named `Urls`. |
|||
|
|||
**Example: Set the root URL and a named URL for the MVC application** |
|||
|
|||
```csharp |
|||
Configure<AppUrlOptions>(options => |
|||
{ |
|||
options.Applications["MVC"].RootUrl = "https://my-app.com"; |
|||
options.Applications["MVC"].Urls["MyPage"] = "my-page"; |
|||
}); |
|||
``` |
|||
|
|||
* `"MVC"` is the **application key**. Some modules (such as Account) register their URLs under a known key — `"MVC"` is the default for the **server-side UI**. You can use any key you want for your own applications. |
|||
* `RootUrl` is the **base URL** of that application. |
|||
* `Urls[urlName]` is a **relative path** appended to `RootUrl`. The final URL is built as `RootUrl.EnsureEndsWith('/') + Urls[urlName]`, so the relative path should **not** start with a `/`. When `RootUrl` is `null`, the value of `Urls[urlName]` is returned as-is. |
|||
|
|||
The Account module, for example, **pre-registers** its URLs in its application module: |
|||
|
|||
**Example: How the Account module registers the password reset URL** |
|||
|
|||
```csharp |
|||
Configure<AppUrlOptions>(options => |
|||
{ |
|||
options.Applications["MVC"].Urls[AccountUrlNames.PasswordReset] = "Account/ResetPassword"; |
|||
}); |
|||
``` |
|||
|
|||
> So configuring `Applications["MVC"].RootUrl` in your own module is usually enough to make password reset and similar Account email links point to the right host. |
|||
|
|||
### Defaults in the application startup template |
|||
|
|||
The ABP **application startup template** wires `Applications["MVC"].RootUrl` to the `App:SelfUrl` setting and seeds `RedirectAllowedUrls` from `App:RedirectAllowedUrls`: |
|||
|
|||
```csharp |
|||
Configure<AppUrlOptions>(options => |
|||
{ |
|||
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"]; |
|||
options.RedirectAllowedUrls.AddRange( |
|||
configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>()); |
|||
}); |
|||
``` |
|||
|
|||
> This is why Account email links point to your **host URL** out of the box: they reuse `App:SelfUrl`. If that default isn't what you want — for example, in a subdomain-based **multi-tenant** setup — override `Applications["MVC"].RootUrl` with the template you need (see [Multi-Tenant Aware URLs](#multi-tenant-aware-urls)). |
|||
|
|||
## Using `IAppUrlProvider` |
|||
|
|||
[Inject](../fundamentals/dependency-injection.md) the `IAppUrlProvider` service into any class that needs to build a cross-application URL. |
|||
|
|||
**Example: Resolve a root URL and a named URL of the MVC application** |
|||
|
|||
```csharp |
|||
public class MyNotificationSender : ITransientDependency |
|||
{ |
|||
private readonly IAppUrlProvider _appUrlProvider; |
|||
|
|||
public MyNotificationSender(IAppUrlProvider appUrlProvider) |
|||
{ |
|||
_appUrlProvider = appUrlProvider; |
|||
} |
|||
|
|||
public async Task SendAsync() |
|||
{ |
|||
var rootUrl = await _appUrlProvider.GetUrlAsync("MVC"); |
|||
var pageUrl = await _appUrlProvider.GetUrlAsync("MVC", "MyPage"); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* `GetUrlAsync(appName)` returns the configured `RootUrl` for the given application. |
|||
* `GetUrlAsync(appName, urlName)` returns the **combined URL** described above. |
|||
* `GetUrlAsync(...)` throws an `AbpException` when the resolved URL is `null` or empty (e.g. both `RootUrl` and `Urls[urlName]` are unset). Use `GetUrlOrNullAsync(...)` if you'd rather get `null` and decide what to do yourself. |
|||
* `NormalizeUrlAsync(url)` applies tenant placeholder substitution to a URL string that you already have. Useful when the URL doesn't come from `AppUrlOptions`. |
|||
|
|||
## Multi-Tenant Aware URLs |
|||
|
|||
If your solution uses **subdomain-based** multi-tenancy (see the [Domain/Subdomain Tenant Resolver](../architecture/multi-tenancy/index.md#domainsubdomain-tenant-resolver)), you'll usually want the **outbound URLs** you generate (email links, redirects) to also be tenant-aware — otherwise the link in a password reset email won't point to the tenant's subdomain. |
|||
|
|||
`AppUrlOptions` supports the following **placeholders** in any URL value. They are substituted by `IAppUrlProvider` based on the **current tenant**: |
|||
|
|||
| Placeholder | Replaced with | |
|||
| --- | --- | |
|||
| `{0}` | Current tenant **name** | |
|||
| `{%{{{ {{tenantName}} }}}%}` | Current tenant **name** | |
|||
| `{%{{{ {{tenantId}} }}}%}` | Current tenant **id** | |
|||
|
|||
The `{0}` placeholder uses the **same convention** as `AddDomainTenantResolver("{0}.example.com")`, so a typical subdomain-tenant setup looks like this: |
|||
|
|||
**Example: Tenant-aware Account email links via a subdomain template** |
|||
|
|||
```csharp |
|||
Configure<AbpTenantResolveOptions>(options => |
|||
{ |
|||
options.AddDomainTenantResolver("{0}.example.com"); |
|||
}); |
|||
|
|||
Configure<AppUrlOptions>(options => |
|||
{ |
|||
options.Applications["MVC"].RootUrl = "https://{0}.example.com"; |
|||
}); |
|||
``` |
|||
|
|||
With this configuration, password reset emails sent to a tenant whose name is `acme` will contain a link starting with `https://acme.example.com/`, matching the tenant's subdomain. |
|||
|
|||
### Host (no tenant) Fallback |
|||
|
|||
When there is **no current tenant** (host-side request), the placeholder **and the dot following it** are removed together: |
|||
|
|||
| Template | Tenant `acme` | Host (no tenant) | |
|||
| --- | --- | --- | |
|||
| `https://{0}.example.com` | `https://acme.example.com` | `https://example.com` | |
|||
| `https://{%{{{ {{tenantId}} }}}%}.example.com` | `https://3a21....example.com` | `https://example.com` | |
|||
|
|||
A single subdomain-style template like the ones above therefore works for **both** tenant and host scenarios without extra configuration. |
|||
|
|||
> If your subdomain is based on the tenant **id** rather than the name, use `https://{%{{{ {{tenantId}} }}}%}.example.com`. The resolver's `{0}` placeholder accepts both name and id when finding a tenant, but `AppUrlOptions` substitutes `{0}` with the tenant **name**; if those two don't match, switch to the explicit `{%{{{ {{tenantId}} }}}%}` form on the `AppUrlOptions` side. |
|||
|
|||
## Redirect Allowed URLs |
|||
|
|||
`AppUrlOptions.RedirectAllowedUrls` is a list of URL entries used by `IAppUrlProvider.IsRedirectAllowedUrlAsync(url)` to decide whether a redirect target is allowed. A URL is allowed when it satisfies **either** of: |
|||
|
|||
* **Prefix match**: the URL string **starts with** a configured entry (case-insensitive). |
|||
* **Subdomain match**: the URL and the entry have the **same scheme** and **port**, and the URL's host **ends with** `.{entry-host}`. |
|||
|
|||
**Example: Register allowed redirect URLs (including a wildcard)** |
|||
|
|||
```csharp |
|||
Configure<AppUrlOptions>(options => |
|||
{ |
|||
options.RedirectAllowedUrls.Add("https://my-app.com"); |
|||
options.RedirectAllowedUrls.Add("https://admin.my-app.com"); |
|||
|
|||
options.RedirectAllowedUrls.Add("https://*.my-app.com"); |
|||
}); |
|||
``` |
|||
|
|||
* A **plain entry** like `https://my-app.com` allows any URL that starts with that prefix, plus any subdomain of `my-app.com`. |
|||
* A **wildcard entry** like `https://*.my-app.com` allows any subdomain of `my-app.com`; the `*.` is stripped before the subdomain check. |
|||
* Entries also go through **tenant placeholder substitution**, so `https://{0}.my-app.com` is resolved to the current tenant's URL first (e.g. `https://acme.my-app.com`) and then compared. Use the wildcard form when you need to allow *any* tenant subdomain regardless of the current tenant. |
|||
|
|||
## See Also |
|||
|
|||
* [Multi-Tenancy](../architecture/multi-tenancy/index.md) |
|||
* [Account Module](../../modules/account.md) |
|||
* [Emailing](emailing.md) |
|||
@ -1,16 +1,17 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Explore ABP's UI options, including MVC, Blazor, Angular, React Native, and MAUI, to build dynamic applications effortlessly." |
|||
"Description": "Explore ABP's UI options, including React, MVC, Blazor, Angular, React Native, and MAUI, to build dynamic applications effortlessly." |
|||
} |
|||
``` |
|||
|
|||
# ABP UI Options |
|||
|
|||
ABP provides several options for building the user interface (UI) in your applications. Here are some of the officially supported UI options you can use with ABP: |
|||
ABP provides several options for building the user interface (UI) in your applications. React is part of the **modern template system**. Here are some of the officially supported UI options you can use with ABP: |
|||
|
|||
* [React](./react/index.md) *(modern template system only)* |
|||
* [MVC / Razor Pages](./mvc-razor-pages/overall.md) |
|||
* [Blazor](./blazor/overall.md) |
|||
* [Angular](./angular/quick-start.md) |
|||
* [React Native](./react-native/index.md) |
|||
* [MAUI](./maui/index.md) |
|||
* [MAUI](./maui/index.md) |
|||
|
|||
@ -0,0 +1,191 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how the ABP Admin Console works with React UI applications and how it is hosted under /admin-console." |
|||
} |
|||
``` |
|||
|
|||
# Admin Console |
|||
|
|||
The **ABP Admin Console** is the React-based administration UI for ABP applications. It provides management pages for ABP modules and is available in React UI solutions created with ABP Studio v3.0+ or `abp new --modern --ui-framework react`. |
|||
|
|||
The Admin Console is delivered as the `Volo.Abp.AdminConsole` NuGet package for layered and single-layer solutions. In microservice solutions, the template also includes a standalone `apps/react-admin-console/` React app. |
|||
|
|||
## What It Provides |
|||
|
|||
The Admin Console contains administration pages for the ABP modules included in the host application. Module pages are activated based on the backend services available in the host, so a solution only shows pages for modules it actually has. |
|||
|
|||
The built-in module areas include: |
|||
|
|||
| Module | Notes | |
|||
| --- | --- | |
|||
| Identity Pro | User, role, claim, and organization unit management when Identity services are available. | |
|||
| Account Pro | Account management pages and account-related flows. | |
|||
| OpenIddict | Application and scope management when OpenIddict services are available. | |
|||
| Audit Logging UI | Optional. Visible when Audit Logging services are available. | |
|||
| AI Management | Optional. Visible when AI Management services are available. | |
|||
| Text Template Management | Optional. Visible when Text Template Management services are available. | |
|||
|
|||
Other module pages, such as Setting Management, SaaS, GDPR, or customization pages, can also be available depending on the solution and installed modules. |
|||
|
|||
## Hosting Model |
|||
|
|||
The Admin Console is served under: |
|||
|
|||
```text |
|||
/admin-console/* |
|||
``` |
|||
|
|||
API endpoints used by the Admin Console are served under: |
|||
|
|||
```text |
|||
/admin-console/api/* |
|||
``` |
|||
|
|||
The `Volo.Abp.AdminConsole` package embeds the built React SPA under `wwwroot/admin-console/` and registers it with ABP's Virtual File System. `AdminConsoleSpaMiddleware` then serves static assets and falls back to `index.html` for client-side routes. |
|||
|
|||
The middleware deliberately lets `/admin-console/api/*` requests pass through to MVC controllers. |
|||
|
|||
## Layered and Single-Layer Templates |
|||
|
|||
For layered and single-layer modern templates: |
|||
|
|||
- The developer-owned React app is in the `react/` folder. |
|||
- The Admin Console UI is embedded in the backend through the `Volo.Abp.AdminConsole` NuGet package. |
|||
- There is no separate `react-admin-console/` source folder in the generated solution. |
|||
- The backend host serves Admin Console pages under `/admin-console/*`. |
|||
|
|||
Example URL: |
|||
|
|||
```text |
|||
https://localhost:44300/admin-console/ |
|||
``` |
|||
|
|||
The main React app links to the Admin Console through `getAdminConsoleUrl()`. |
|||
|
|||
## Microservice Template |
|||
|
|||
For the microservice modern template: |
|||
|
|||
- The main React app is in `apps/react/`. |
|||
- The Admin Console app is in `apps/react-admin-console/`. |
|||
- Both are served through the Web Gateway. |
|||
- The Admin Console has its own OpenIddict client, normally `<ProjectName>_AdminConsole`. |
|||
|
|||
The main React app uses `adminConsoleUrl` from `dynamic-env.json` to open the Admin Console origin and `/admin-console` base path. |
|||
|
|||
## Module Discovery |
|||
|
|||
The Admin Console calls: |
|||
|
|||
```text |
|||
GET /admin-console/api/modules |
|||
``` |
|||
|
|||
The backend checks for module application service contracts and returns which module areas are available. The discovery keys include: |
|||
|
|||
| Key | Backend service check | |
|||
| --- | --- | |
|||
| `identity` | `Volo.Abp.Identity.IIdentityUserAppService` | |
|||
| `saas` | `Volo.Saas.Host.ITenantAppService` | |
|||
| `auditLogging` | `Volo.Abp.AuditLogging.IAuditLogsAppService` | |
|||
| `gdpr` | `Volo.Abp.Gdpr.IGdprRequestAppService` | |
|||
| `openIddict` | `Volo.Abp.OpenIddict.Applications.IApplicationAppService` | |
|||
| `textTemplateManagement` | `Volo.Abp.TextTemplateManagement.TextTemplates.ITemplateDefinitionAppService` | |
|||
| `aiManagement` | AI Management service contracts, with a legacy AI engine fallback. | |
|||
|
|||
`settingManagement` is always returned as available by the discovery endpoint, while access to pages is still controlled by permissions. |
|||
|
|||
## Configuration Endpoint |
|||
|
|||
The Admin Console also uses: |
|||
|
|||
```text |
|||
GET /admin-console/api/config |
|||
``` |
|||
|
|||
This endpoint provides Admin Console runtime settings such as authority, client ID, scopes, application name, customization options, and localization language configuration. |
|||
|
|||
Host applications can configure Admin Console options from the `AdminConsole` configuration section or by configuring `AbpAdminConsoleOptions`. |
|||
|
|||
## Configuring the Admin Console |
|||
|
|||
In layered and single-layer modern React templates, the embedded Admin Console is configured from the backend host application's `appsettings.json` file. The generated template includes an `AdminConsole` section similar to the following: |
|||
|
|||
```json |
|||
{ |
|||
"AdminConsole": { |
|||
"IsEnabled": true, |
|||
"RedirectRootToAdminConsole": true, |
|||
"Authority": "https://localhost:44300", |
|||
"ClientId": "Acme_BookStore_AdminConsole", |
|||
"Scope": "openid profile email offline_access Acme_BookStore", |
|||
"LocalizationLanguages": [ "en", "tr" ], |
|||
"ThemeOverrideCssPath": "/theme-override.css", |
|||
"InitialTheme": "system", |
|||
"CustomizationPermissionName": "AdminConsole.Customization" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
You can also configure the same values in the module class with `AbpAdminConsoleOptions`: |
|||
|
|||
```csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAdminConsoleOptions>(options => |
|||
{ |
|||
options.IsEnabled = true; |
|||
options.RedirectRootToAdminConsole = true; |
|||
options.Authority = "https://localhost:44300"; |
|||
options.ClientId = "Acme_BookStore_AdminConsole"; |
|||
options.Scope = "openid profile email offline_access Acme_BookStore"; |
|||
options.LocalizationLanguages = new[] { "en", "tr" }; |
|||
options.ThemeOverrideCssPath = "/theme-override.css"; |
|||
options.InitialTheme = "system"; |
|||
options.CustomizationPermissionName = "AdminConsole.Customization"; |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
The most commonly changed options are: |
|||
|
|||
| Option | Description | |
|||
| --- | --- | |
|||
| `IsEnabled` | Enables or disables the embedded Admin Console SPA middleware. | |
|||
| `RedirectRootToAdminConsole` | Redirects the backend root path (`/`) to `/admin-console`. | |
|||
| `Authority` | OpenID Connect authority URL. If it is `null`, the host origin is used. | |
|||
| `ClientId` | OpenIddict client ID used by the Admin Console SPA. | |
|||
| `Scope` | Space-separated OAuth scopes requested by the Admin Console. | |
|||
| `LocalizationLanguages` | UI language codes exposed to the Admin Console. If empty, the frontend falls back to `en`. | |
|||
| `ThemeOverrideCssPath` | Optional CSS path or absolute URL injected into the Admin Console HTML. | |
|||
| `InitialTheme` | Initial theme behavior: `light`, `dark`, `system`, or `both`. | |
|||
| `CustomizationPermissionName` | Permission required to show and use the Admin Console customization page. If not set, customization is disabled. | |
|||
|
|||
The `ApplicationName`, `LogoUrl`, `InitialTheme`, and `ThemeOverrideCssPath` values can also be changed from the Admin Console customization UI when `CustomizationPermissionName` is configured and the current user has that permission. Values saved from the customization UI are stored as settings and override the defaults from configuration. |
|||
|
|||
In microservice solutions, the Admin Console is a separate React app under `apps/react-admin-console/`. It still uses its own OpenIddict client (`<ProjectName>_AdminConsole`) and runtime configuration, while the backend exposes the same `/admin-console/api/config` and `/admin-console/api/modules` endpoints. |
|||
|
|||
## Permissions |
|||
|
|||
Admin Console routes still require permissions. For example: |
|||
|
|||
- Identity pages use `AbpIdentity.*` permissions. |
|||
- OpenIddict pages use `OpenIddictPro.Application` and `OpenIddictPro.Scope`. |
|||
- Audit Logging uses `AuditLogging.AuditLogs`. |
|||
- Text Template Management uses `TextTemplateManagement.*`. |
|||
- AI Management uses `AIManagement.*`. |
|||
|
|||
The main React app's Admin Console menu item only requires authentication. The Admin Console performs detailed permission checks for its own pages. |
|||
|
|||
## Customization |
|||
|
|||
The developer-owned React app is intended for application-specific pages. The Admin Console is an ABP-managed administration surface and should normally be updated by updating ABP packages. |
|||
|
|||
For layered and single-layer hosts, the package supports host-side options such as application name, localization languages, and theme override CSS path. For larger UI changes, prefer building your own pages in the main React app or extending the backend modules through supported ABP extension points. |
|||
|
|||
## See Also |
|||
|
|||
- [React UI](./index.md) |
|||
- [Environment Variables](./environment-variables.md) |
|||
- [Permission Management](./permission-management.md) |
|||
@ -0,0 +1,183 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how authentication and authorization are configured in ABP React UI applications." |
|||
} |
|||
``` |
|||
|
|||
# Authorization in React UI |
|||
|
|||
OAuth is preconfigured in ABP React UI templates. When you create a React solution with ABP Studio v3.0+ or `abp new --modern --ui-framework react`, the template includes OpenID Connect settings, an OpenIddict client, route guards, and authentication hooks. |
|||
|
|||
The React app authenticates against the ABP Auth Server using the **Authorization Code flow with PKCE**, which is the recommended flow for browser-based applications. |
|||
|
|||
## Packages |
|||
|
|||
The template uses these packages for authentication: |
|||
|
|||
| Package | Purpose | |
|||
| --- | --- | |
|||
| `@volo/abp-oidc-auth` | Framework-agnostic OIDC client helpers for ABP/OpenIddict backends. | |
|||
| `@volo/abp-react-oidc-auth` | React adapter for the ABP OIDC client. | |
|||
| `oidc-client-ts` | Underlying OIDC protocol implementation. | |
|||
|
|||
The package list also includes `@volo/abp-app-config` and `@volo/abp-react-app-config`, which are used to fetch application configuration and permissions after authentication. |
|||
|
|||
## OAuth Configuration |
|||
|
|||
The OIDC settings are resolved from runtime configuration first and fall back to `src/env.ts`. |
|||
|
|||
```ts |
|||
export function getOAuthConfig(): { |
|||
issuer: string |
|||
redirectUri: string |
|||
clientId: string |
|||
scope: string |
|||
responseType: 'code' |
|||
} { |
|||
return { |
|||
issuer: loadedConfig?.oAuthConfig?.issuer ?? env.oauth.issuer, |
|||
redirectUri: loadedConfig?.oAuthConfig?.redirectUri ?? env.oauth.redirectUri, |
|||
clientId: loadedConfig?.oAuthConfig?.clientId ?? env.oauth.clientId, |
|||
scope: loadedConfig?.oAuthConfig?.scope ?? env.oauth.scope, |
|||
responseType: 'code', |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The important configuration values are: |
|||
|
|||
- `oAuthConfig.issuer`: Auth Server / OpenIddict authority URL. |
|||
- `oAuthConfig.redirectUri`: URL where the Auth Server redirects after login. |
|||
- `oAuthConfig.clientId`: OpenIddict client ID, normally `<ProjectName>_App`. |
|||
- `oAuthConfig.scope`: Scopes requested by the React app. |
|||
|
|||
See [Environment Variables](./environment-variables.md) for the full runtime configuration model. |
|||
|
|||
## Initializing Authentication |
|||
|
|||
The app loads runtime configuration before initializing OIDC: |
|||
|
|||
```tsx |
|||
async function bootstrap() { |
|||
await loadRuntimeConfig() |
|||
initUserManager() |
|||
createRoot(document.getElementById('root')!).render( |
|||
<StrictMode> |
|||
<App /> |
|||
</StrictMode> |
|||
) |
|||
} |
|||
``` |
|||
|
|||
`initUserManager()` creates the ABP React OIDC client: |
|||
|
|||
```ts |
|||
client = createAbpReactOidcAuth({ |
|||
authority: config.issuer, |
|||
clientId: config.clientId, |
|||
redirectUri: config.redirectUri, |
|||
postLogoutRedirectUri: config.redirectUri, |
|||
scope: config.scope, |
|||
responseType: config.responseType, |
|||
automaticSilentRenew: true, |
|||
userStoreType: 'localStorage', |
|||
userStorePrefix: `oidc.${config.clientId}`, |
|||
silentRedirectUri: `${window.location.origin}/silent-renew.html`, |
|||
}) |
|||
``` |
|||
|
|||
The template stores the OIDC user in local storage and enables silent renewal with `public/silent-renew.html`. |
|||
|
|||
## Auth Provider and Hook |
|||
|
|||
`AuthProvider` wraps the app and handles the OIDC callback: |
|||
|
|||
```tsx |
|||
export function AuthProvider({ children }: { children: ReactNode }) { |
|||
const authClient = getAuthClient() |
|||
|
|||
useEffect(() => { |
|||
const params = new URLSearchParams(window.location.search) |
|||
if (!params.has('code') || !params.has('state')) return |
|||
void authClient.handleSigninCallback().then(() => |
|||
window.history.replaceState({}, document.title, window.location.pathname) |
|||
) |
|||
}, []) |
|||
|
|||
return <authClient.AuthProvider>{children}</authClient.AuthProvider> |
|||
} |
|||
``` |
|||
|
|||
Use `useAuth()` in components: |
|||
|
|||
```tsx |
|||
import { useAuth } from '@/lib/auth/AuthContext' |
|||
|
|||
export function LoginButton() { |
|||
const { isAuthenticated, isLoading, login, logout, user } = useAuth() |
|||
|
|||
if (isLoading) return null |
|||
|
|||
return isAuthenticated ? ( |
|||
<button onClick={() => void logout()}>{user?.name ?? 'Logout'}</button> |
|||
) : ( |
|||
<button onClick={() => void login()}>Login</button> |
|||
) |
|||
} |
|||
``` |
|||
|
|||
## Route Protection |
|||
|
|||
The React template uses TanStack Router. Protected routes use `beforeLoad` guards. |
|||
|
|||
```ts |
|||
const identityLayoutRoute = createRoute({ |
|||
getParentRoute: () => rootRoute, |
|||
path: '/identity', |
|||
component: IdentityLayout, |
|||
beforeLoad: authGuard, |
|||
}) |
|||
``` |
|||
|
|||
`authGuard` checks the current OIDC user and redirects unauthenticated users to the Auth Server: |
|||
|
|||
```ts |
|||
export async function authGuard({ location }: GuardContext) { |
|||
const user = await userManager.getUser() |
|||
if (!user || user.expired) { |
|||
await userManager.signinRedirect({ |
|||
state: { returnUrl: location.href }, |
|||
}) |
|||
throw new Error('Redirecting to login') |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Routes that also require a permission use `createPermissionGuard`: |
|||
|
|||
```ts |
|||
const usersRoute = createRoute({ |
|||
getParentRoute: () => identityLayoutRoute, |
|||
path: 'users', |
|||
component: UsersPage, |
|||
beforeLoad: createPermissionGuard('AbpIdentity.Users'), |
|||
}) |
|||
``` |
|||
|
|||
Permission checks are explained in [Permission Management](./permission-management.md). |
|||
|
|||
## OpenIddict Clients |
|||
|
|||
The generated OpenIddict clients depend on the template: |
|||
|
|||
- Layered and single-layer modern templates use the main React client, normally `<ProjectName>_App`. |
|||
- Microservice modern templates also include an Admin Console client, normally `<ProjectName>_AdminConsole`, because the Admin Console is a separate React app. |
|||
|
|||
If you change URLs after generation, update both the runtime configuration and the corresponding OpenIddict client redirect URLs. |
|||
|
|||
## See Also |
|||
|
|||
- [Environment Variables](./environment-variables.md) |
|||
- [Permission Management](./permission-management.md) |
|||
- [Authorization](../../../framework/fundamentals/authorization/index.md) |
|||
@ -0,0 +1,184 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn about the component architecture and UI libraries used by ABP React UI applications." |
|||
} |
|||
``` |
|||
|
|||
# Components |
|||
|
|||
ABP React UI templates use a source-owned component architecture. The generated app includes shadcn/ui-style primitives, layout components, feature components, route pages, and shared infrastructure under `src/lib/`. |
|||
|
|||
The goal is to give you a working React application that you can customize without replacing framework-owned black boxes. |
|||
|
|||
## Component Structure |
|||
|
|||
The main React app is organized like this: |
|||
|
|||
```text |
|||
src/ |
|||
├── components/ |
|||
│ ├── layout/ |
|||
│ ├── ui/ |
|||
│ └── identity/ |
|||
├── lib/ |
|||
│ ├── api/ |
|||
│ ├── auth/ |
|||
│ ├── i18n/ |
|||
│ ├── routing/ |
|||
│ └── theme/ |
|||
├── locales/ |
|||
├── pages/ |
|||
└── routes/ |
|||
``` |
|||
|
|||
The exact folders can vary by selected template options and modules. |
|||
|
|||
## UI Stack |
|||
|
|||
The React template uses: |
|||
|
|||
| Library | Purpose | |
|||
| --- | --- | |
|||
| React | UI rendering. | |
|||
| Vite | Build tool and development server. | |
|||
| TanStack Router | Client-side routing. | |
|||
| TanStack Query | Server state, queries, mutations, and cache invalidation. | |
|||
| shadcn/ui-style components | Source-owned UI primitives built on Radix UI and Tailwind CSS. | |
|||
| Radix UI | Accessible low-level UI primitives. | |
|||
| Tailwind CSS | Utility-first styling and design tokens. | |
|||
| React Hook Form | Form state management. | |
|||
| Zod | Form and DTO validation schemas. | |
|||
| Axios | HTTP client. | |
|||
| i18next / react-i18next | Localization. | |
|||
| Zustand | Lightweight client state when needed. | |
|||
| Sonner | Toast notifications. | |
|||
| Lucide React | Icons. | |
|||
|
|||
## `components/ui` |
|||
|
|||
`src/components/ui/` contains reusable UI primitives. These components are copied into your project and can be edited directly. |
|||
|
|||
Common components include: |
|||
|
|||
- `Button` |
|||
- `Input` |
|||
- `Label` |
|||
- `Table` |
|||
- `Dialog` |
|||
- `DropdownMenu` |
|||
- `Select` |
|||
- `Card` |
|||
- `Tabs` |
|||
- `Badge` |
|||
- `DatePicker` |
|||
- `ConfirmDialog` |
|||
|
|||
Use these primitives to build application pages and feature components. |
|||
|
|||
```tsx |
|||
import { Button } from '@/components/ui/button' |
|||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' |
|||
|
|||
export function ReportCard() { |
|||
return ( |
|||
<Card> |
|||
<CardHeader> |
|||
<CardTitle>Reports</CardTitle> |
|||
</CardHeader> |
|||
<CardContent> |
|||
<Button>Refresh</Button> |
|||
</CardContent> |
|||
</Card> |
|||
) |
|||
} |
|||
``` |
|||
|
|||
## Layout Components |
|||
|
|||
Layout components are under `src/components/layout/`. |
|||
|
|||
Important components include: |
|||
|
|||
- `RootLayout`: root shell used by TanStack Router. |
|||
- `Header`: top bar, login button, theme toggle, and user menu. |
|||
- `Sidebar`: route-config-driven navigation menu. |
|||
- `UserMenu`: account-related dropdown menu. |
|||
|
|||
The sidebar reads `src/lib/routing/route-config.ts`, checks authentication and permissions, and renders internal or external links. |
|||
|
|||
## Feature Components |
|||
|
|||
Feature-specific components should live near the feature that owns them. For example, Identity-specific layout components live under `src/components/identity/`, while Books-specific UI is implemented in `src/pages/books/BooksPage.tsx` in the sample template. |
|||
|
|||
As a rule: |
|||
|
|||
- Put generic, reusable primitives in `components/ui`. |
|||
- Put application layout in `components/layout`. |
|||
- Put feature-specific components under `components/<feature>` or next to the page when they are only used by one page. |
|||
|
|||
## Pages |
|||
|
|||
Route pages live under `src/pages/`. A page usually combines: |
|||
|
|||
- UI primitives from `components/ui`. |
|||
- API functions from `src/lib/api`. |
|||
- Server state from TanStack Query. |
|||
- Form state from React Hook Form. |
|||
- Validation schemas from Zod. |
|||
- Permissions from `usePermissions()`. |
|||
- Localized strings from `useTranslation()`. |
|||
|
|||
The Books page is the best full CRUD reference when the sample CRUD option is selected. |
|||
|
|||
## Forms |
|||
|
|||
Forms use React Hook Form and Zod: |
|||
|
|||
```tsx |
|||
const productSchema = z.object({ |
|||
name: z.string().min(1, 'Required'), |
|||
price: z.number().min(0), |
|||
}) |
|||
|
|||
type ProductFormData = z.infer<typeof productSchema> |
|||
|
|||
const form = useForm<ProductFormData>({ |
|||
resolver: zodResolver(productSchema), |
|||
defaultValues: { |
|||
name: '', |
|||
price: 0, |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
This keeps runtime validation and TypeScript types close to each other. |
|||
|
|||
## Routing Components |
|||
|
|||
Routes are configured in `src/routes/router.tsx` with TanStack Router. Use: |
|||
|
|||
- `authGuard` for authenticated pages. |
|||
- `createPermissionGuard('Permission.Name')` for permission-protected pages. |
|||
- `RootLayout` and nested layouts for shared page structure. |
|||
|
|||
Menu entries are configured separately in `src/lib/routing/route-config.ts`, so route registration and navigation display can evolve independently. |
|||
|
|||
## API Components and Hooks |
|||
|
|||
API functions live under `src/lib/api/` and use the shared `api` Axios instance. Components normally consume these functions through TanStack Query: |
|||
|
|||
```tsx |
|||
const usersQuery = useQuery({ |
|||
queryKey: ['app', 'users', queryParams], |
|||
queryFn: () => getAppUsers(queryParams), |
|||
}) |
|||
``` |
|||
|
|||
This keeps HTTP details out of rendering components and gives you caching, loading states, refetching, and mutation invalidation. |
|||
|
|||
## See Also |
|||
|
|||
- [Customization](../customization.md) |
|||
- [HTTP Requests](../http-requests.md) |
|||
- [Unit Testing](../unit-testing.md) |
|||
@ -0,0 +1,208 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how to customize ABP React UI applications, including pages, themes, sidebar navigation, and the user menu." |
|||
} |
|||
``` |
|||
|
|||
# Customization |
|||
|
|||
The React app generated by ABP is fully owned by your solution. All source code is available, so you can change pages, components, routes, themes, menus, API calls, and layout behavior just like in any other React application. |
|||
|
|||
This page focuses on the main developer-owned React app. The same general approach applies to the public-web React app if your solution includes one. The Admin Console is an ABP-managed administration surface; see [Admin Console](./admin-console.md) for details. |
|||
|
|||
## General Customization |
|||
|
|||
Application pages live under `src/pages/`. The template includes practical references: |
|||
|
|||
- **Users page**: a simple page that lists users and links to the Admin Console for full user and role management. |
|||
- **Books page**: a full CRUD sample when the sample CRUD option is selected during solution creation. It demonstrates TanStack Query, forms, Zod validation, dialogs, tables, permissions, and toast notifications. |
|||
|
|||
Shared UI and infrastructure live under: |
|||
|
|||
```text |
|||
src/ |
|||
├── components/ |
|||
│ ├── layout/ |
|||
│ └── ui/ |
|||
├── lib/ |
|||
│ ├── api/ |
|||
│ ├── auth/ |
|||
│ ├── i18n/ |
|||
│ ├── routing/ |
|||
│ └── theme/ |
|||
└── pages/ |
|||
``` |
|||
|
|||
## Adding a Page |
|||
|
|||
Create a page under `src/pages/`: |
|||
|
|||
```tsx |
|||
export function ReportsPage() { |
|||
return ( |
|||
<div className="space-y-6"> |
|||
<h1 className="text-3xl font-bold tracking-tight">Reports</h1> |
|||
</div> |
|||
) |
|||
} |
|||
``` |
|||
|
|||
Register it with TanStack Router in `src/routes/router.tsx`: |
|||
|
|||
```tsx |
|||
const reportsRoute = createRoute({ |
|||
getParentRoute: () => rootRoute, |
|||
path: '/reports', |
|||
component: ReportsPage, |
|||
beforeLoad: createPermissionGuard('MyProjectName.Reports'), |
|||
}) |
|||
|
|||
const routeTree = rootRoute.addChildren([ |
|||
indexRoute, |
|||
reportsRoute, |
|||
]) |
|||
``` |
|||
|
|||
Use `authGuard` for pages that only require authentication and `createPermissionGuard` for pages that require a permission. |
|||
|
|||
## Theming |
|||
|
|||
The React template uses **shadcn/ui**-style components, Radix UI primitives, Tailwind CSS, and CSS variables. |
|||
|
|||
Theme tokens are defined in `src/styles/globals.css`: |
|||
|
|||
```css |
|||
:root { |
|||
--background: oklch(0.978 0.003 264); |
|||
--foreground: oklch(0.205 0.008 264); |
|||
--primary: oklch(0.48 0.10 278); |
|||
--radius: 0.5rem; |
|||
} |
|||
|
|||
.dark { |
|||
--background: oklch(0.16 0.004 264); |
|||
--foreground: oklch(0.92 0.005 264); |
|||
--primary: oklch(0.62 0.12 278); |
|||
} |
|||
``` |
|||
|
|||
ABP Studio's modern wizard can generate different shadcn theme color presets and light/dark/system theme behavior. |
|||
|
|||
## Changing Theme Colors |
|||
|
|||
To make a quick theme change, edit the CSS variables in `src/styles/globals.css`: |
|||
|
|||
```css |
|||
:root { |
|||
--primary: oklch(0.623 0.188 259.6); |
|||
--primary-foreground: oklch(1 0 0); |
|||
} |
|||
``` |
|||
|
|||
Because the generated shadcn/ui components consume these variables through Tailwind tokens, the change applies across buttons, links, active sidebar entries, focus rings, and other components that use the primary color. |
|||
|
|||
## Theme Mode Switcher |
|||
|
|||
Theme mode is handled by `src/lib/theme/ThemeProvider.tsx`. It supports: |
|||
|
|||
- `light` |
|||
- `dark` |
|||
- `system` |
|||
|
|||
The header cycles through the allowed modes: |
|||
|
|||
```tsx |
|||
const THEME_CYCLE: Theme[] = ['light', 'dark', 'system'] |
|||
|
|||
function ThemeToggle() { |
|||
const { theme, resolvedTheme, setTheme } = useTheme() |
|||
|
|||
function cycleTheme() { |
|||
const currentIndex = THEME_CYCLE.indexOf(theme) |
|||
const nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % THEME_CYCLE.length |
|||
setTheme(THEME_CYCLE[nextIndex]) |
|||
} |
|||
|
|||
return <Button variant="ghost" size="icon" onClick={cycleTheme}>...</Button> |
|||
} |
|||
``` |
|||
|
|||
To remove the switcher or replace it with a dropdown, edit `src/components/layout/Header.tsx`. |
|||
|
|||
## Modifying the Sidebar Menu |
|||
|
|||
Sidebar navigation is defined in `src/lib/routing/route-config.ts`. |
|||
|
|||
Add a menu item: |
|||
|
|||
```ts |
|||
import { BarChart3 } from 'lucide-react' |
|||
|
|||
export const routeConfig: RouteConfigItem[] = [ |
|||
{ |
|||
path: '/reports', |
|||
nameKey: 'Menu:Reports', |
|||
icon: BarChart3, |
|||
order: 10, |
|||
requiredPolicy: 'MyProjectName.Reports', |
|||
}, |
|||
] |
|||
``` |
|||
|
|||
Then add the localization key to `src/locales/en.json`: |
|||
|
|||
```json |
|||
{ |
|||
"Menu:Reports": "Reports" |
|||
} |
|||
``` |
|||
|
|||
Use these properties depending on the menu item: |
|||
|
|||
| Property | Use | |
|||
| --- | --- | |
|||
| `path` | Internal route path or logical path for an external item. | |
|||
| `nameKey` | Localization key shown in the sidebar. | |
|||
| `icon` | Optional Lucide icon. | |
|||
| `order` | Sorting order. | |
|||
| `requiredPolicy` | Hide the item unless the permission is granted. | |
|||
| `requiresAuth` | Hide the item unless the user is authenticated. | |
|||
| `externalHref` | Open an external URL or another app, such as the Admin Console. | |
|||
| `children` | Add nested sidebar items. | |
|||
|
|||
## Sidebar vs User Menu |
|||
|
|||
Use the **sidebar navigation** for application pages and module entry points. |
|||
|
|||
Use the **user menu** for account-specific actions, profile links, sessions, security logs, linked accounts, and logout. The user menu is implemented in `src/components/layout/UserMenu.tsx`. |
|||
|
|||
Example user menu item: |
|||
|
|||
```tsx |
|||
<DropdownMenuItem asChild className="cursor-pointer"> |
|||
<a href="/account/preferences"> |
|||
<Settings className="size-4" /> |
|||
{t('MyAccount::Preferences')} |
|||
</a> |
|||
</DropdownMenuItem> |
|||
``` |
|||
|
|||
## Customizing UI Components |
|||
|
|||
shadcn/ui components are copied into your project under `src/components/ui/`. They are not black-box components from a package. You can edit them directly. |
|||
|
|||
For example: |
|||
|
|||
- Change button variants in `src/components/ui/button.tsx`. |
|||
- Change dialog structure in `src/components/ui/dialog.tsx`. |
|||
- Add a new reusable component under `src/components/ui/`. |
|||
- Add feature-specific components under `src/components/<feature>/`. |
|||
|
|||
Keep generic primitives in `components/ui` and business-specific components close to the feature or page that owns them. |
|||
|
|||
## See Also |
|||
|
|||
- [Components](./components/index.md) |
|||
- [Permission Management](./permission-management.md) |
|||
- [Admin Console](./admin-console.md) |
|||
@ -0,0 +1,121 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how runtime configuration and environment variables work in ABP React UI applications." |
|||
} |
|||
``` |
|||
|
|||
# Environment Variables |
|||
|
|||
ABP React UI applications use a runtime configuration file and Vite environment variables together. The template is preconfigured by ABP Studio's modern wizard, available with ABP Studio **v3.0+**, so a newly created solution already contains working local values for the API, Auth Server, OpenIddict client, and Admin Console link. |
|||
|
|||
You usually change these values when moving the application to another environment such as staging or production. |
|||
|
|||
## Configuration Sources |
|||
|
|||
The React template reads configuration from these places: |
|||
|
|||
- `dynamic-env.json`: runtime configuration that can be changed without rebuilding the application. |
|||
- `public/dynamic-env.json`: the file served by the app. The Vite build copies the root `dynamic-env.json` into this location when it exists. |
|||
- `src/env.ts`: local fallback values used when runtime configuration is not loaded. |
|||
- `.env` files / shell variables: Vite variables such as `VITE_API_URL`, `VITE_AUTH_URL`, and `VITE_APP_URL`. |
|||
|
|||
For layered and single-layer modern templates, the React app is in the `react/` folder. For the microservice modern template, it is in `apps/react/`. |
|||
|
|||
## `dynamic-env.json` |
|||
|
|||
The runtime configuration file has the same purpose as Angular's dynamic environment configuration: it lets you deploy the same build artifact to different environments and change the API or authentication endpoints at runtime. |
|||
|
|||
```json |
|||
{ |
|||
"application": { |
|||
"baseUrl": "https://localhost:3000", |
|||
"name": "Acme.BookStore" |
|||
}, |
|||
"oAuthConfig": { |
|||
"issuer": "https://localhost:44301/", |
|||
"redirectUri": "https://localhost:3000", |
|||
"clientId": "Acme_BookStore_App", |
|||
"scope": "offline_access openid profile email phone AuthServer IdentityService AdministrationService" |
|||
}, |
|||
"apis": { |
|||
"default": { |
|||
"url": "https://localhost:44300", |
|||
"rootNamespace": "Acme.BookStore" |
|||
} |
|||
}, |
|||
"adminConsoleUrl": "https://localhost:44307" |
|||
} |
|||
``` |
|||
|
|||
The template loads `/dynamic-env.json` first and then tries `/getEnvConfig` for compatibility with environments that expose the file through that endpoint. |
|||
|
|||
## Available Values |
|||
|
|||
| Key | Description | |
|||
| --- | --- | |
|||
| `application.baseUrl` | Public URL of the React application. It is used as a fallback for OAuth redirect URLs. | |
|||
| `application.name` | Application name. | |
|||
| `application.logoUrl` | Optional logo URL for application branding. | |
|||
| `oAuthConfig.issuer` | Auth Server / OpenIddict authority URL. | |
|||
| `oAuthConfig.redirectUri` | Redirect URI registered for the React OpenIddict client. | |
|||
| `oAuthConfig.clientId` | OpenIddict client ID. The main React app uses `<ProjectName>_App`. | |
|||
| `oAuthConfig.scope` | OAuth scopes requested by the SPA. | |
|||
| `apis.default.url` | Backend API base URL. In microservice solutions, this normally points to the Web Gateway. | |
|||
| `apis.default.rootNamespace` | Root namespace used by generated API code and module-specific clients. | |
|||
| `adminConsoleUrl` | Origin of the Admin Console app. The React template uses it to open `/admin-console`. | |
|||
|
|||
The `DynamicEnv` type also includes fields such as `production`, `oAuthConfig.requireHttps`, `oAuthConfig.responseType`, `oAuthConfig.strictDiscoveryDocumentValidation`, and `oAuthConfig.skipIssuerCheck`. The template's OIDC setup always uses the Authorization Code flow by setting `responseType` to `code`. |
|||
|
|||
## Vite Variables |
|||
|
|||
The React template uses Vite and reads environment variables with `loadEnv(mode, process.cwd(), '')`, so variables are not limited to the `VITE_` prefix inside `vite.config.ts`. |
|||
|
|||
The important variables for developers are: |
|||
|
|||
| Variable | Description | |
|||
| --- | --- | |
|||
| `VITE_API_URL` | Overrides the backend API or gateway URL used by the dev proxy and runtime fallback. | |
|||
| `VITE_AUTH_URL` | Overrides the Auth Server URL used by the dev proxy and runtime fallback. If omitted, the dev proxy can fall back to `VITE_API_URL`. | |
|||
| `VITE_APP_URL` | Overrides the React app URL used as the OAuth redirect URI fallback. | |
|||
|
|||
Example: |
|||
|
|||
```bash |
|||
VITE_API_URL=https://api.bookstore.example.com |
|||
VITE_AUTH_URL=https://auth.bookstore.example.com |
|||
VITE_APP_URL=https://bookstore.example.com |
|||
``` |
|||
|
|||
## What ABP Studio Preconfigures |
|||
|
|||
When a React solution is created with ABP Studio v3.0+ or `abp new --modern`, the template fills these values from the generated solution configuration: |
|||
|
|||
- Local launch ports for the React app, Web Gateway/API host, Auth Server, and Admin Console. |
|||
- The OpenIddict client ID, usually `<ProjectName>_App`. |
|||
- OAuth scopes based on the selected modules, such as Identity, Administration, SaaS, Audit Logging, GDPR, File Management, AI Management, Language Management, or Chat. |
|||
- `adminConsoleUrl` when the template includes a separate Admin Console application. |
|||
|
|||
For local development, these generated values should work without manual changes. For production, update the API URL, Auth Server URL, redirect URI, client ID if you changed the seeded client, and any environment-specific scopes. |
|||
|
|||
## Development Proxy |
|||
|
|||
In development, `vite.config.ts` proxies these paths: |
|||
|
|||
- `/api` to `VITE_API_URL` or the generated API/gateway URL. |
|||
- `/connect` to `VITE_AUTH_URL`, `VITE_API_URL`, or the generated Auth Server URL. |
|||
- `/getEnvConfig` to `VITE_API_URL` or the generated API/gateway URL. |
|||
|
|||
This allows the React app to call same-origin paths during development while the backend services run on their own ports. |
|||
|
|||
## Deployment |
|||
|
|||
For deployment, prefer changing `dynamic-env.json` instead of rebuilding the React application for each environment. The file should be served with `application/json` content type and should not be rewritten to `index.html` by SPA fallback rules. |
|||
|
|||
If your server exposes `/getEnvConfig`, configure it to return the same JSON content as `dynamic-env.json`. |
|||
|
|||
## See Also |
|||
|
|||
- [React UI](./index.md) |
|||
- [Authorization](./authorization.md) |
|||
- [HTTP Requests](./http-requests.md) |
|||
@ -0,0 +1,213 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how HTTP requests are made in ABP React UI applications with Axios, runtime configuration, and ABP interceptors." |
|||
} |
|||
``` |
|||
|
|||
# HTTP Requests |
|||
|
|||
ABP React UI templates use [Axios](https://axios-http.com/) for HTTP requests. The generated app contains a shared Axios instance with ABP-specific request and response interceptors, plus typed API modules for backend endpoints. |
|||
|
|||
The shared client is defined in `src/lib/api/axios.ts` and exported as `api`. |
|||
|
|||
## Base URL |
|||
|
|||
The Axios base URL is resolved at request time from runtime configuration: |
|||
|
|||
```ts |
|||
export function getApiBaseUrl(): string { |
|||
const apiUrl = getApiUrl() |
|||
if (apiUrl.startsWith('http://') || apiUrl.startsWith('https://')) { |
|||
return apiUrl.replace(/\/$/, '') + '/api' |
|||
} |
|||
if (import.meta.env.DEV) { |
|||
return '/api' |
|||
} |
|||
return apiUrl.replace(/\/$/, '') + '/api' |
|||
} |
|||
``` |
|||
|
|||
The API URL comes from: |
|||
|
|||
1. `dynamic-env.json` -> `apis.default.url` |
|||
2. `VITE_API_URL` |
|||
3. `src/env.ts` generated fallback |
|||
|
|||
In microservice solutions, `apis.default.url` normally points to the Web Gateway. In layered and single-layer solutions, it normally points to the HTTP API host. |
|||
|
|||
## Shared Axios Instance |
|||
|
|||
The template creates one shared instance: |
|||
|
|||
```ts |
|||
export const api = axios.create({ |
|||
baseURL: '', |
|||
headers: { |
|||
'X-Requested-With': 'XMLHttpRequest', |
|||
'Content-Type': 'application/json', |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
Use this instance for application API modules instead of creating new Axios clients. It centralizes ABP headers, authentication, tenant handling, language handling, and redirects. |
|||
|
|||
## Request Interceptor |
|||
|
|||
Before each request, the template: |
|||
|
|||
- Sets `baseURL` from runtime configuration. |
|||
- Adds `Authorization: Bearer <token>` from the OIDC user. |
|||
- Adds `__tenant` when the user has selected a tenant. |
|||
- Adds `Accept-Language` from i18next. |
|||
- Keeps default AJAX headers such as `X-Requested-With`. |
|||
|
|||
```ts |
|||
api.interceptors.request.use(async (config) => { |
|||
config.baseURL = getApiBaseUrl() |
|||
|
|||
const user = await userManager.getUser() |
|||
if (user?.access_token) { |
|||
config.headers.Authorization = `Bearer ${user.access_token}` |
|||
} |
|||
|
|||
const tenantId = sessionStorage.getItem('abp_tenant_id') |
|||
if (tenantId && !config.headers.__tenant) { |
|||
config.headers.__tenant = tenantId |
|||
} |
|||
|
|||
if (i18n?.language) { |
|||
config.headers['Accept-Language'] = |
|||
config.headers['Accept-Language'] ?? i18n.language |
|||
} |
|||
|
|||
return config |
|||
}) |
|||
``` |
|||
|
|||
## Response Interceptor |
|||
|
|||
The response interceptor handles common authorization failures: |
|||
|
|||
- `401 Unauthorized`: redirects to login unless `skipAuthRedirect` is set. |
|||
- `403 Forbidden`: redirects to `/403` unless `skip403Redirect` is set. |
|||
- Other errors are rejected so the caller can handle them. |
|||
|
|||
```ts |
|||
api.interceptors.response.use( |
|||
(response) => response, |
|||
async (error) => { |
|||
const status = error.response?.status |
|||
|
|||
if (status === 401 && !error.config?.skipAuthRedirect) { |
|||
await userManager.signinRedirect() |
|||
return Promise.reject(new Error('Unauthorized - redirecting to login')) |
|||
} |
|||
|
|||
if (status === 403 && !error.config?.skip403Redirect) { |
|||
window.location.href = '/403' |
|||
return Promise.reject(new Error('Forbidden')) |
|||
} |
|||
|
|||
return Promise.reject(error) |
|||
} |
|||
) |
|||
``` |
|||
|
|||
Use `skipAuthRedirect` or `skip403Redirect` for calls where the component should handle the error itself. |
|||
|
|||
## Typed API Modules |
|||
|
|||
The template organizes backend calls under `src/lib/api/`. For example, the Books sample defines DTOs and functions in `books.ts`: |
|||
|
|||
```ts |
|||
import { api } from './axios' |
|||
|
|||
export interface PagedResultDto<T> { |
|||
items: T[] |
|||
totalCount: number |
|||
} |
|||
|
|||
export interface BookDto { |
|||
id: string |
|||
name?: string |
|||
price: number |
|||
} |
|||
|
|||
export async function getBooks(): Promise<PagedResultDto<BookDto>> { |
|||
const { data } = await api.get<PagedResultDto<BookDto>>('/app/book', { |
|||
params: { |
|||
maxResultCount: 10, |
|||
skipCount: 0, |
|||
}, |
|||
}) |
|||
return data |
|||
} |
|||
``` |
|||
|
|||
Notice that the API module calls `/app/book`, not `/api/app/book`. The shared Axios base URL already includes the `/api` prefix when needed. |
|||
|
|||
## Using Requests from Components |
|||
|
|||
The template uses TanStack Query for server state: |
|||
|
|||
```tsx |
|||
const { data, isLoading } = useQuery({ |
|||
queryKey: ['books', skipCount], |
|||
queryFn: () => |
|||
getBooks({ |
|||
maxResultCount: 10, |
|||
skipCount, |
|||
sorting: 'creationTime desc', |
|||
}), |
|||
}) |
|||
``` |
|||
|
|||
Mutations use `useMutation` and invalidate related queries after success: |
|||
|
|||
```tsx |
|||
const createMutation = useMutation({ |
|||
mutationFn: createBook, |
|||
onSuccess: () => { |
|||
queryClient.invalidateQueries({ queryKey: ['books'] }) |
|||
toast.success(t('AbpUi::SavedSuccessfully')) |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
## Adding a New API Module |
|||
|
|||
Create a file under `src/lib/api/`: |
|||
|
|||
```ts |
|||
import { api } from './axios' |
|||
|
|||
export interface ProductDto { |
|||
id: string |
|||
name: string |
|||
} |
|||
|
|||
export async function getProducts(): Promise<ProductDto[]> { |
|||
const { data } = await api.get<ProductDto[]>('/app/product') |
|||
return data |
|||
} |
|||
``` |
|||
|
|||
Then consume it from a component with TanStack Query: |
|||
|
|||
```tsx |
|||
const productsQuery = useQuery({ |
|||
queryKey: ['products'], |
|||
queryFn: getProducts, |
|||
}) |
|||
``` |
|||
|
|||
## Development Proxy |
|||
|
|||
In development, Vite proxies `/api`, `/connect`, and `/getEnvConfig`. This lets the React app use same-origin paths while calls are forwarded to the backend, Auth Server, or gateway configured by `VITE_API_URL` and `VITE_AUTH_URL`. |
|||
|
|||
## See Also |
|||
|
|||
- [Environment Variables](./environment-variables.md) |
|||
- [Authorization](./authorization.md) |
|||
- [Permission Management](./permission-management.md) |
|||
@ -0,0 +1,142 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how to build modern web applications with ABP React UI, including runtime configuration, authentication, Admin Console, shadcn/ui components, and testing." |
|||
} |
|||
``` |
|||
|
|||
# React UI |
|||
|
|||
ABP provides a **React UI** option for building modern, client-side web applications. React UI is part of the **modern template system** and is available with **ABP Studio v3.0+** through the Modern Wizard or with `abp new --modern` using [ABP CLI](../../../cli/index.md). |
|||
|
|||
React UI is not available in classic, non-modern templates. Use ABP Studio's modern template flow or `Volo.Abp.Studio.Cli` to create a React-based solution. |
|||
|
|||
## Technology Stack |
|||
|
|||
The React UI template is built with: |
|||
|
|||
| Technology | Purpose | |
|||
| --- | --- | |
|||
| [Vite](https://vite.dev/) | Build tool and dev server | |
|||
| [React](https://react.dev/) | UI framework | |
|||
| [TanStack Router](https://tanstack.com/router) | Client-side routing | |
|||
| [TanStack Query](https://tanstack.com/query) | Server state and API request orchestration | |
|||
| [shadcn/ui](https://ui.shadcn.com/) | Source-owned component library built on Radix UI and Tailwind CSS | |
|||
| [Zod](https://zod.dev/) | Schema validation | |
|||
| [React Hook Form](https://react-hook-form.com/) | Form state management | |
|||
| [Axios](https://axios-http.com/) | HTTP client | |
|||
| [Vitest](https://vitest.dev/) | Unit testing | |
|||
| [OpenID Connect / OIDC](https://openid.net/connect/) | Authentication against the ABP Auth Server | |
|||
|
|||
The template also includes ABP-specific NPM packages. These packages are maintained by ABP and published on npm; use the following links to view their package details: |
|||
|
|||
- [View `@volo/abp-app-config` on npm](https://www.npmjs.com/package/@volo/abp-app-config) |
|||
- [View `@volo/abp-oidc-auth` on npm](https://www.npmjs.com/package/@volo/abp-oidc-auth) |
|||
- [View `@volo/abp-react-app-config` on npm](https://www.npmjs.com/package/@volo/abp-react-app-config) |
|||
- [View `@volo/abp-react-oidc-auth` on npm](https://www.npmjs.com/package/@volo/abp-react-oidc-auth) |
|||
|
|||
## React App and Admin Console |
|||
|
|||
A modern React solution contains two UI surfaces: |
|||
|
|||
- **Your React application**: the developer-owned SPA where you build application-specific pages and features. |
|||
- **ABP Admin Console**: the React-based administration UI for ABP modules. |
|||
|
|||
The Admin Console is provided by the `Volo.Abp.AdminConsole` NuGet package in layered and single-layer templates. In microservice templates, it is also generated as a separate `apps/react-admin-console/` app and served through the Web Gateway. |
|||
|
|||
See [Admin Console](./admin-console.md) for hosting, module discovery, and permission details. |
|||
|
|||
## Solution Structure |
|||
|
|||
The React app location depends on the modern template type: |
|||
|
|||
- **Layered (`app --modern`) and single-layer (`app-nolayers --modern`)**: the React app lives in the `react/` folder at the solution root. |
|||
- **Microservice (`microservice --modern`)**: the React app lives at `apps/react/`. |
|||
|
|||
Typical structure: |
|||
|
|||
```text |
|||
react/ |
|||
├── dynamic-env.json |
|||
├── public/ |
|||
├── src/ |
|||
│ ├── components/ |
|||
│ ├── lib/ |
|||
│ ├── locales/ |
|||
│ ├── pages/ |
|||
│ ├── routes/ |
|||
│ └── main.tsx |
|||
├── package.json |
|||
├── vite.config.ts |
|||
└── vitest.config.ts |
|||
``` |
|||
|
|||
## Creating a Solution |
|||
|
|||
Install or update `Volo.Abp.Studio.Cli`, then create a modern solution: |
|||
|
|||
```bash |
|||
# Layered app with React UI |
|||
abp new Acme.BookStore --template app --modern --ui-framework react |
|||
|
|||
# Single-layer app with React UI |
|||
abp new Acme.BookStore --template app-nolayers --modern --ui-framework react |
|||
|
|||
# Microservice solution with React UI |
|||
abp new Acme.BookStore --template microservice --modern --ui-framework react |
|||
``` |
|||
|
|||
You can also use ABP Studio v3.0+ and select the modern template flow in the New Solution wizard. The wizard preconfigures local ports, runtime configuration, OIDC clients, theme options, and React/Admin Console wiring based on the selected template and modules. |
|||
|
|||
## Running the Application |
|||
|
|||
Start the backend from ABP Studio or by running the backend host projects, then start the React development server. |
|||
|
|||
For layered and single-layer templates: |
|||
|
|||
```bash |
|||
cd react |
|||
npm install |
|||
npm run dev |
|||
``` |
|||
|
|||
For microservice templates: |
|||
|
|||
```bash |
|||
cd apps/react |
|||
npm install |
|||
npm run dev |
|||
``` |
|||
|
|||
Run tests with: |
|||
|
|||
```bash |
|||
npm run test |
|||
``` |
|||
|
|||
Build for production with: |
|||
|
|||
```bash |
|||
npm run build |
|||
``` |
|||
|
|||
## Documentation Map |
|||
|
|||
Use these pages to learn each part of the React UI: |
|||
|
|||
- [Environment Variables](./environment-variables.md): runtime configuration, `dynamic-env.json`, Vite variables, and Studio-generated defaults. |
|||
- [Authorization](./authorization.md): OIDC, Authorization Code flow with PKCE, auth provider, hooks, and route guards. |
|||
- [Localization](./localization.md): i18next, local JSON resources, ABP localization keys, and request culture. |
|||
- [Permission Management](./permission-management.md): fetching granted policies, `usePermissions()`, route protection, and conditional UI. |
|||
- [HTTP Requests](./http-requests.md): Axios setup, interceptors, typed API modules, and TanStack Query usage. |
|||
- [Customization](./customization.md): changing pages, themes, sidebar items, user menu entries, and shadcn/ui components. |
|||
- [Components](./components/index.md): component architecture, UI primitives, layout components, forms, and routing. |
|||
- [Unit Testing](./unit-testing.md): Vitest, React Testing Library, examples, and test workflow. |
|||
- [Admin Console](./admin-console.md): the `Volo.Abp.AdminConsole` package, `/admin-console/*` hosting, module discovery, and optional modules. |
|||
|
|||
## See Also |
|||
|
|||
- [ABP Studio](../../../studio/index.md) |
|||
- [ABP CLI](../../../cli/index.md) |
|||
- [Authorization](../../../framework/fundamentals/authorization/index.md) |
|||
- [Localization](../../../framework/fundamentals/localization.md) |
|||
@ -0,0 +1,158 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how localization works in ABP React UI applications with i18next and ABP application configuration." |
|||
} |
|||
``` |
|||
|
|||
# Localization |
|||
|
|||
ABP React UI templates use [i18next](https://www.i18next.com/) with [react-i18next](https://react.i18next.com/). The generated app includes local JSON resources and integrates with ABP application configuration through the `@volo/abp-app-config` packages. |
|||
|
|||
## Localization Files |
|||
|
|||
The main React app stores client-side translations under `src/locales/`. |
|||
|
|||
```text |
|||
src/ |
|||
├── locales/ |
|||
│ └── en.json |
|||
└── lib/ |
|||
└── i18n/ |
|||
└── i18n.ts |
|||
``` |
|||
|
|||
The default `i18n.ts` imports the English resource and registers it: |
|||
|
|||
```ts |
|||
import i18n from 'i18next' |
|||
import { initReactI18next } from 'react-i18next' |
|||
import en from '@/locales/en.json' |
|||
|
|||
i18n.use(initReactI18next).init({ |
|||
resources: { |
|||
en: { translation: en }, |
|||
}, |
|||
lng: 'en', |
|||
fallbackLng: 'en', |
|||
keySeparator: false, |
|||
nsSeparator: false, |
|||
interpolation: { |
|||
escapeValue: false, |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
`keySeparator` and `nsSeparator` are disabled so ABP-style keys such as `AbpIdentity::Users` and `Menu:Home` can be used directly. |
|||
|
|||
## Using Localized Text |
|||
|
|||
Use `useTranslation()` from `react-i18next` in components: |
|||
|
|||
```tsx |
|||
import { useTranslation } from 'react-i18next' |
|||
|
|||
export function BooksTitle() { |
|||
const { t } = useTranslation() |
|||
|
|||
return <h1>{t('Menu:Books')}</h1> |
|||
} |
|||
``` |
|||
|
|||
ABP localization keys commonly use the `ResourceName::Key` format: |
|||
|
|||
```tsx |
|||
{t('AbpIdentity::Users')} |
|||
{t('AbpAccount::Login')} |
|||
{t('AbpUi::SavedSuccessfully')} |
|||
``` |
|||
|
|||
Application-specific menu keys may use names like `Menu:Home` or `Menu:Books`. |
|||
|
|||
## Adding a Translation |
|||
|
|||
Add the key to `src/locales/en.json`: |
|||
|
|||
```json |
|||
{ |
|||
"Menu:Reports": "Reports", |
|||
"Reports": "Reports" |
|||
} |
|||
``` |
|||
|
|||
Then use it from a component: |
|||
|
|||
```tsx |
|||
const { t } = useTranslation() |
|||
|
|||
return <h1>{t('Reports')}</h1> |
|||
``` |
|||
|
|||
## Adding a Language |
|||
|
|||
Create a new JSON file, for example `src/locales/tr.json`: |
|||
|
|||
```json |
|||
{ |
|||
"Menu:Reports": "Raporlar", |
|||
"Reports": "Raporlar" |
|||
} |
|||
``` |
|||
|
|||
Register it in `src/lib/i18n/i18n.ts`: |
|||
|
|||
```ts |
|||
import en from '@/locales/en.json' |
|||
import tr from '@/locales/tr.json' |
|||
|
|||
i18n.use(initReactI18next).init({ |
|||
resources: { |
|||
en: { translation: en }, |
|||
tr: { translation: tr }, |
|||
}, |
|||
lng: 'en', |
|||
fallbackLng: 'en', |
|||
}) |
|||
``` |
|||
|
|||
If you add a language selector, call `i18n.changeLanguage('tr')` when the user chooses Turkish. |
|||
|
|||
## Server-Side ABP Localization |
|||
|
|||
ABP's backend localization system is still the source of truth for server-defined resources, validation messages, exception messages, and module texts. The React app uses ABP application configuration through `@volo/abp-app-config` / `@volo/abp-react-app-config` for auth and configuration data, and these packages can include localization resources when configured to do so. |
|||
|
|||
The main template currently creates the app configuration client with: |
|||
|
|||
```ts |
|||
export const appConfig = createAbpReactAppConfig({ |
|||
baseUrl: () => getApiUrl(), |
|||
includeLocalizationResources: false, |
|||
}) |
|||
``` |
|||
|
|||
Because `includeLocalizationResources` is disabled in the main React template, UI text is normally loaded from `src/locales/*.json`. If you enable server-provided localization resources, make sure your UI initialization merges them into i18next before rendering localized components. |
|||
|
|||
## Request Culture |
|||
|
|||
The shared Axios client sends the active i18next language with each request: |
|||
|
|||
```ts |
|||
if (i18n?.language) { |
|||
config.headers['Accept-Language'] = |
|||
config.headers['Accept-Language'] ?? i18n.language |
|||
} |
|||
``` |
|||
|
|||
This lets backend responses, validation messages, and exception messages use the selected culture when the server supports it. |
|||
|
|||
## Admin Console Localization |
|||
|
|||
The Admin Console has its own React app and localization setup. In layered and single-layer templates, it is served from the `Volo.Abp.AdminConsole` package. In microservice templates, it is generated as `apps/react-admin-console/`. |
|||
|
|||
The Admin Console host can expose available languages through `AdminConsole:LocalizationLanguages`, and `/admin-console/api/config` returns the normalized language list. |
|||
|
|||
## See Also |
|||
|
|||
- [React UI](./index.md) |
|||
- [HTTP Requests](./http-requests.md) |
|||
- [Localization](../../../framework/fundamentals/localization.md) |
|||
@ -0,0 +1,171 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how permissions are fetched, stored, checked, and applied in ABP React UI applications." |
|||
} |
|||
``` |
|||
|
|||
# Permission Management |
|||
|
|||
ABP permissions are defined on the server side and are exposed to the React app through ABP application configuration. The React template uses those permissions to protect routes, hide sidebar items, and conditionally render UI actions. |
|||
|
|||
For the server-side permission system, see [Authorization](../../../framework/fundamentals/authorization/index.md). |
|||
|
|||
## Packages |
|||
|
|||
The React template uses: |
|||
|
|||
| Package | Purpose | |
|||
| --- | --- | |
|||
| `@volo/abp-app-config` | Framework-agnostic ABP application configuration client. | |
|||
| `@volo/abp-react-app-config` | React hooks and adapters for application configuration. | |
|||
|
|||
The template creates a shared app configuration client in `src/lib/auth/permissions.ts`: |
|||
|
|||
```ts |
|||
export const appConfig = createAbpReactAppConfig({ |
|||
baseUrl: () => getApiUrl(), |
|||
includeLocalizationResources: false, |
|||
}) |
|||
``` |
|||
|
|||
## Fetching Permissions |
|||
|
|||
After the user logs in, `AuthProvider` fetches application configuration with the current access token: |
|||
|
|||
```ts |
|||
const user = await authClient.getUserManager().getUser() |
|||
if (user && !user.expired) { |
|||
await fetchAppConfig(user.access_token ?? null) |
|||
} |
|||
``` |
|||
|
|||
`fetchAppConfig` also sends the current tenant ID when one is selected: |
|||
|
|||
```ts |
|||
export async function fetchAppConfig(token: string | null): Promise<void> { |
|||
const headers: Record<string, string> = {} |
|||
const tenantId = sessionStorage.getItem('abp_tenant_id') |
|||
if (tenantId) headers.__tenant = tenantId |
|||
await appConfig.fetchConfig(token, { headers }) |
|||
} |
|||
``` |
|||
|
|||
The response includes the current user's granted policies. These are stored by the app configuration client and exposed to React components. |
|||
|
|||
## Checking Permissions in Components |
|||
|
|||
Use `usePermissions()` from `src/lib/auth/permissions.ts`: |
|||
|
|||
```tsx |
|||
import { usePermissions } from '@/lib/auth/permissions' |
|||
|
|||
export function BookActions() { |
|||
const { isGranted } = usePermissions() |
|||
|
|||
return ( |
|||
<> |
|||
{isGranted('MyProjectName.Books.Edit') && <button>Edit</button>} |
|||
{isGranted('MyProjectName.Books.Delete') && <button>Delete</button>} |
|||
</> |
|||
) |
|||
} |
|||
``` |
|||
|
|||
The Books page uses this pattern for edit and delete actions: |
|||
|
|||
```ts |
|||
const { isGranted } = usePermissions() |
|||
const canEdit = isGranted('MyProjectName.Books.Edit') |
|||
const canDelete = isGranted('MyProjectName.Books.Delete') |
|||
``` |
|||
|
|||
## Route Guards |
|||
|
|||
Routes can require a permission by using `createPermissionGuard`: |
|||
|
|||
```ts |
|||
const booksRoute = createRoute({ |
|||
getParentRoute: () => rootRoute, |
|||
path: '/books', |
|||
component: BooksPage, |
|||
beforeLoad: createPermissionGuard('MyProjectName.Books'), |
|||
}) |
|||
``` |
|||
|
|||
`createPermissionGuard` runs the authentication guard first, fetches app configuration if needed, and redirects to `/403` when the required policy is not granted. |
|||
|
|||
```ts |
|||
export function createPermissionGuard(requiredPolicy: string) { |
|||
return async (context: GuardContext) => { |
|||
await authGuard(context) |
|||
|
|||
if (!appConfig.getSnapshot()?.initialized) { |
|||
const user = await userManager.getUser() |
|||
await fetchAppConfig(user?.access_token ?? null) |
|||
} |
|||
|
|||
if (!isPolicyGranted(requiredPolicy)) throw redirect({ to: '/403' }) |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Sidebar Visibility |
|||
|
|||
The sidebar reads `routeConfig` and hides items that require missing permissions: |
|||
|
|||
```ts |
|||
export const routeConfig: RouteConfigItem[] = [ |
|||
{ |
|||
path: '/identity/users', |
|||
nameKey: 'AbpIdentity::Users', |
|||
requiredPolicy: 'AbpIdentity.Users', |
|||
}, |
|||
] |
|||
``` |
|||
|
|||
The sidebar checks each item: |
|||
|
|||
```ts |
|||
if (item.requiresAuth && !isAuthenticated) return false |
|||
if (!item.requiredPolicy) return true |
|||
if (!isAuthenticated) return false |
|||
return isGranted(item.requiredPolicy) |
|||
``` |
|||
|
|||
Use `requiresAuth` for menu items that only require login. Use `requiredPolicy` when the item should only be visible to users with a specific permission. |
|||
|
|||
## Compound Policies |
|||
|
|||
The template's `isPolicyGranted` helper supports simple compound expressions: |
|||
|
|||
- `PermissionA || PermissionB` |
|||
- `PermissionA && PermissionB` |
|||
|
|||
This is useful for menu entries that should be visible when the user has one of several related module permissions. |
|||
|
|||
## Where Permissions Are Applied |
|||
|
|||
The generated React app uses permissions in these places: |
|||
|
|||
- **Users page**: the `/identity/users` route and sidebar entry require `AbpIdentity.Users`. The page links to the Admin Console for full user and role management. |
|||
- **Books page**: the route requires `MyProjectName.Books`; edit and delete actions check `MyProjectName.Books.Edit` and `MyProjectName.Books.Delete`. |
|||
- **Admin Console link**: the sidebar entry uses `requiresAuth` because the Admin Console performs its own module and route permission checks. |
|||
|
|||
The Admin Console applies module-specific permissions for pages such as: |
|||
|
|||
- Identity users and roles: `AbpIdentity.*`. |
|||
- OpenIddict applications and scopes: `OpenIddictPro.Application` and `OpenIddictPro.Scope`. |
|||
- Audit Logging UI: `AuditLogging.AuditLogs`. |
|||
- Text Template Management: `TextTemplateManagement.*`. |
|||
- AI Management: `AIManagement.*`. |
|||
|
|||
## Multi-Tenancy |
|||
|
|||
When a tenant is selected, the template stores the tenant ID in `sessionStorage` as `abp_tenant_id`. Permission and API requests send it with the `__tenant` header. This ensures the backend returns permissions and data for the selected tenant context. |
|||
|
|||
## See Also |
|||
|
|||
- [Authorization](./authorization.md) |
|||
- [HTTP Requests](./http-requests.md) |
|||
- [Authorization](../../../framework/fundamentals/authorization/index.md) |
|||
@ -0,0 +1,150 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how to run and write unit tests in ABP React UI applications with Vitest and React Testing Library." |
|||
} |
|||
``` |
|||
|
|||
# Unit Testing React UI |
|||
|
|||
ABP React UI templates are preconfigured for unit testing. A solution created with ABP Studio v3.0+ or `abp new --modern --ui-framework react` includes Vitest, jsdom, React Testing Library, and jest-dom. |
|||
|
|||
You can add a test file and run the test command without adding extra test infrastructure. |
|||
|
|||
## Test Stack |
|||
|
|||
The React template uses: |
|||
|
|||
| Package | Purpose | |
|||
| --- | --- | |
|||
| `vitest` | Test runner and assertion library. | |
|||
| `jsdom` | Browser-like DOM environment for component tests. | |
|||
| `@testing-library/react` | Render React components and query the DOM like a user. | |
|||
| `@testing-library/jest-dom` | Extra DOM assertions such as `toBeInTheDocument`. | |
|||
|
|||
The template also includes `src/test/setup.ts`, which imports `@testing-library/jest-dom/vitest` and initializes the React i18n setup. |
|||
|
|||
## Configuration |
|||
|
|||
The test configuration is in `vitest.config.ts`: |
|||
|
|||
```ts |
|||
import { defineConfig } from 'vitest/config' |
|||
import react from '@vitejs/plugin-react' |
|||
import path from 'path' |
|||
|
|||
export default defineConfig({ |
|||
plugins: [react()], |
|||
test: { |
|||
environment: 'jsdom', |
|||
setupFiles: ['./src/test/setup.ts'], |
|||
include: ['src/**/*.{test,spec}.{ts,tsx}'], |
|||
globals: true, |
|||
}, |
|||
resolve: { |
|||
alias: { |
|||
'@': path.resolve(__dirname, './src'), |
|||
}, |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
Tests can import application files with the same `@/` alias used by the app. |
|||
|
|||
## Running Tests |
|||
|
|||
Install dependencies once: |
|||
|
|||
```bash |
|||
npm install |
|||
``` |
|||
|
|||
Run tests in watch mode: |
|||
|
|||
```bash |
|||
npm run test |
|||
``` |
|||
|
|||
Run tests once, which is useful for CI: |
|||
|
|||
```bash |
|||
npm run test:run |
|||
``` |
|||
|
|||
The template's `package.json` maps these commands to `vitest` and `vitest run`. |
|||
|
|||
## Example Test |
|||
|
|||
The template includes example tests under `src/`. For example, `src/pages/home/HomePage.test.tsx` renders the home page and mocks the authentication hook: |
|||
|
|||
```tsx |
|||
import { describe, it, expect, vi, beforeEach } from 'vitest' |
|||
import { render, screen } from '@testing-library/react' |
|||
import { HomePage } from './HomePage' |
|||
import * as auth from '@/lib/auth/AuthContext' |
|||
|
|||
vi.mock('@/lib/auth/AuthContext', () => ({ |
|||
useAuth: vi.fn(), |
|||
})) |
|||
|
|||
describe('HomePage', () => { |
|||
beforeEach(() => { |
|||
vi.clearAllMocks() |
|||
}) |
|||
|
|||
it('renders login prompt when not authenticated', () => { |
|||
vi.mocked(auth.useAuth).mockReturnValue({ |
|||
isAuthenticated: false, |
|||
isLoading: false, |
|||
user: null, |
|||
login: vi.fn(), |
|||
logout: vi.fn(), |
|||
navigateToLogin: vi.fn(), |
|||
getAccessToken: vi.fn(), |
|||
} as unknown as ReturnType<typeof auth.useAuth>) |
|||
|
|||
render(<HomePage />) |
|||
expect(screen.getByText('Welcome')).toBeInTheDocument() |
|||
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument() |
|||
}) |
|||
}) |
|||
``` |
|||
|
|||
This style keeps the test focused on visible behavior. Dependencies that would require real authentication, network calls, or browser redirects are mocked. |
|||
|
|||
## Writing a Component Test |
|||
|
|||
Create a `*.test.tsx` file next to the component: |
|||
|
|||
```tsx |
|||
import { render, screen } from '@testing-library/react' |
|||
import { describe, expect, it } from 'vitest' |
|||
import { Button } from '@/components/ui/button' |
|||
|
|||
describe('Button', () => { |
|||
it('renders its content', () => { |
|||
render(<Button>Save</Button>) |
|||
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument() |
|||
}) |
|||
}) |
|||
``` |
|||
|
|||
Prefer queries such as `getByRole`, `getByLabelText`, and `getByText` because they describe what the user can see or do. |
|||
|
|||
## Writing a Service or Hook Test |
|||
|
|||
For non-component logic, use Vitest directly. The template includes tests for routing guards, permissions, authentication context, and Axios interceptors. |
|||
|
|||
When testing API code, mock the shared Axios instance or the lower-level dependency instead of calling a real backend. When testing permission behavior, mock the application configuration client or use the exported permission helpers. |
|||
|
|||
## Interpreting Output |
|||
|
|||
Vitest reports each test file, failed assertions, stack traces, and a summary of passed/failed tests. In watch mode, it reruns affected tests when files change. In `test:run` mode, Vitest exits with a non-zero status code if any test fails, which makes it suitable for CI pipelines. |
|||
|
|||
If a component test fails because an ABP service is not initialized, mock the hook or provider used by the component. For example, pages that call `useAuth()` or `usePermissions()` should provide a controlled mock for those hooks unless the test is specifically verifying the provider. |
|||
|
|||
## See Also |
|||
|
|||
- [Components](./components/index.md) |
|||
- [Authorization](./authorization.md) |
|||
- [Permission Management](./permission-management.md) |
|||
|
After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 6.4 KiB |