@ -0,0 +1,82 @@ |
|||
# ABP.IO Platform 9.1 Final Has Been Released! |
|||
|
|||
We are glad to announce that [ABP](https://abp.io/) 9.1 stable version has been released today. |
|||
|
|||
## What's New With Version 9.1? |
|||
|
|||
All the new features were explained in detail in the [9.1 RC Announcement Post](https://abp.io/community/articles/abp-platform-9.1-rc-has-been-released-wws5l00k), so there is no need to review them again. You can check it out for more details. |
|||
|
|||
## Getting Started with 9.1 |
|||
|
|||
### Creating New Solutions |
|||
|
|||
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) to create new solutions. |
|||
|
|||
By default, ABP Studio uses stable versions to create solutions. Therefore, it will be creating the solution with the latest stable version, which is v9.1 for now, so you don't need to specify the version. |
|||
|
|||
### How to Upgrade an Existing Solution |
|||
|
|||
You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained: |
|||
|
|||
### Upgrading via ABP Studio |
|||
|
|||
If you are already using the ABP Studio, you can upgrade it to the latest version to align it with ABP v9.1. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info. |
|||
|
|||
After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution: |
|||
|
|||
 |
|||
|
|||
### Upgrading via ABP CLI |
|||
|
|||
Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version. |
|||
|
|||
If you haven't installed it yet, you can run the following command: |
|||
|
|||
```bash |
|||
dotnet tool install -g Volo.Abp.Studio.Cli |
|||
``` |
|||
|
|||
Or to update the existing CLI, you can run the following command: |
|||
|
|||
```bash |
|||
dotnet tool update -g Volo.Abp.Studio.Cli |
|||
``` |
|||
|
|||
After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows: |
|||
|
|||
```bash |
|||
abp update |
|||
``` |
|||
|
|||
You can run this command in the root folder of your solution to update all ABP related packages. |
|||
|
|||
## Migration Guides |
|||
|
|||
There are a few breaking changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v9.0: [ABP Version 9.1 Migration Guide](https://abp.io/docs/latest/release-info/migration-guides/abp-9-1) |
|||
|
|||
## Community News |
|||
|
|||
### New ABP Community Articles |
|||
|
|||
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: |
|||
|
|||
* [URL-Based Localization](https://abp.io/community/articles/urlbased-localization-3ivzinbb) by [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) |
|||
* [Building a CRUD API with ABP Framework, ASP.NET Core, and PostgreSQL](https://abp.io/community/articles/building-a-crud-api-with-abp-framework-asp.net-core-and-postgresql-elrj0old) by [Berkan Şaşmaz](https://github.com/berkansasmaz) |
|||
* [Encryption and Decryption in ABP Framework](https://abp.io/community/articles/encryption-and-decryption-in-abp-framework-37uqhdwz) by [Liming Ma](https://github.com/maliming) |
|||
* [Migrate Your DB from the Web Application - Adding a DB Migration Controller](https://abp.io/community/articles/migrate-your-db-from-the-web-application-adding-a-db-migration-controller-in-abp-framework-x3u3uvk3) by [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) |
|||
* [Containerization: Blazor WASM + JWT Web API => Docker](https://abp.io/community/articles/containerization-blazor-wasm-jwt-web-api-docker-i3eirlsf) by [Bart Van Hoey](https://abp.io/community/members/bartvanhoey) |
|||
* [Configuring Post-Logout Redirect URI in ABP Based Blazor Applications with OpenIddict](https://abp.io/community/articles/configuring-postlogout-redirect-uri-in-abp-based-blazor-applications-with-openiddict-1t84suxg) by [Engincan Veske](https://github.com/EngincanV) |
|||
|
|||
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/submit) to the ABP Community. |
|||
|
|||
### ABP Community Talks 2025.2: Real World Problems and Solutions with AI |
|||
|
|||
 |
|||
|
|||
In this episode of ABP Community Talks (2025.2), Decision Tree joined us to explore how AI is being leveraged to solve real-world problems, showcasing a practical use case of AI applications. |
|||
|
|||
> You can re-watch the talk from [here](https://www.youtube.com/watch?v=CXpWjxCIY_E). |
|||
|
|||
## About the Next Version |
|||
|
|||
The next feature version will be 9.2. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. |
|||
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 482 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 498 KiB |
|
Before Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 304 KiB |
@ -1,59 +1,118 @@ |
|||
BASTA! Mainz 2023 has wrapped up, and what an extraordinary journey it has been! We can’t wait to share our impressions, highlights, and the incredible impact it had on the tech community in Germany and beyond. |
|||
|
|||
### A Glance Back at BASTA! Mainz 2023
|
|||
|
|||
|
|||
We just came back from the[ BASTA! Conference 2023](https://basta.net/), which is an incredible .NET event with over 600 in-person attendees and an additional 200+ tuning in virtually from across the globe. The buzz of excitement and anticipation in the air was undeniable, setting the stage for an unforgettable event for [ABP.IO](https://abp.io/). |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
As one of the proud sponsors, we set up our booth, engaged with attendees, and delved into a wide range of sessions, workshops, and keynotes to absorb the latest software development trends. |
|||
|
|||
### Engaging with Enthusiastic Minds
|
|||
|
|||
|
|||
As the lead developers of ABP Core Team, *[Alper](https://twitter.com/alperebicoglu)* and *[Ismail](https://twitter.com/ismcagdas)* presented the ABP.IO platform modules and features were quite busy enjoying their presenting work with latest version of demos. Alper also gave a great speech on “Building Multi-tenant ASP.NET Core Application & the ABP Framework” at the BASTA! Mainz conference. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
We introduced **[ABP Framework](https://abp.io/)**, community-driven open-source web application framework, and **[ABP Commercial](https://commercial.abp.io/)**, our enterprise-ready web development platform that is built on top of the open-source ABP Framework to the crowds. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
Apart from that, we were thrilled to give away the **[ABP Commercial Licenses](https://commercial.abp.io/pricing)** to the eager attendees on the venue, including the Raffle prize, **Meta Quest 2**, on our last day in BASTA! Conference. |
|||
|
|||
 |
|||
|
|||
We are profoundly satisfied with the huge interest and engagement shown by the attendees. The sense of community, collaboration, and shared passion for .NET solutions was palpable throughout the event. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
### A Great Partnership with LIS GmbH
|
|||
|
|||
|
|||
 |
|||
|
|||
At BASTA! Mainz 2023, we were particularly thrilled to celebrate our collaboration with ***LIS GmbH***, a leading software solutions provider. This partnership added a unique dimension to the conference, brought us a fresh perspective to the conference, fostering innovation and opening up new avenues for attendees. |
|||
|
|||
### A Big Thank-You from ABP Team
|
|||
|
|||
|
|||
 |
|||
|
|||
Now, a shout-out to BASTA! Mainz 2023. We want to thank to everyone who contributed to the success of this event – attendees, speakers, sponsors, and partners.The conference may have ended, but the knowledge gained, connections formed, and inspiration ignited will continue to shape the tech landscape for years to come. |
|||
|
|||
Until next time! |
|||
BASTA! Mainz 2023 has wrapped up, and what an extraordinary journey it has been! We can’t wait to share our impressions, highlights, and the incredible impact it had on the tech community in Germany and beyond. |
|||
|
|||
|
|||
|
|||
### A Glance Back at BASTA! Mainz 2023 |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
We just came back from the[ BASTA! Conference 2023](https://basta.net/), which is an incredible .NET event with over 600 in-person attendees and an additional 200+ tuning in virtually from across the globe. The buzz of excitement and anticipation in the air was undeniable, setting the stage for an unforgettable event for [ABP.IO](https://abp.io/). |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
As one of the proud sponsors, we set up our booth, engaged with attendees, and delved into a wide range of sessions, workshops, and keynotes to absorb the latest software development trends. |
|||
|
|||
|
|||
|
|||
### Engaging with Enthusiastic Minds |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
As the lead developers of ABP Core Team, *[Alper](https://twitter.com/alperebicoglu)* and *[Ismail](https://twitter.com/ismcagdas)* presented the ABP.IO platform modules and features were quite busy enjoying their presenting work with latest version of demos. Alper also gave a great speech on “Building Multi-tenant ASP.NET Core Application & the ABP Framework” at the BASTA! Mainz conference. |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
We introduced **[ABP Framework](https://abp.io/)**, community-driven open-source web application framework, and **[ABP Commercial](https://commercial.abp.io/)**, our enterprise-ready web development platform that is built on top of the open-source ABP Framework to the crowds. |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
Apart from that, we were thrilled to give away the **[ABP Commercial Licenses](https://commercial.abp.io/pricing)** to the eager attendees on the venue, including the Raffle prize, **Meta Quest 2**, on our last day in BASTA! Conference. |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
We are profoundly satisfied with the huge interest and engagement shown by the attendees. The sense of community, collaboration, and shared passion for .NET solutions was palpable throughout the event. |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
### A Great Partnership with LIS GmbH |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
|
|||
At BASTA! Mainz 2023, we were particularly thrilled to celebrate our collaboration with ***LIS GmbH***, a leading software solutions provider. This partnership added a unique dimension to the conference, brought us a fresh perspective to the conference, fostering innovation and opening up new avenues for attendees. |
|||
|
|||
|
|||
|
|||
### A Big Thank-You from ABP Team |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
Now, a shout-out to BASTA! Mainz 2023. We want to thank to everyone who contributed to the success of this event – attendees, speakers, sponsors, and partners.The conference may have ended, but the knowledge gained, connections formed, and inspiration ignited will continue to shape the tech landscape for years to come. |
|||
|
|||
|
|||
|
|||
Until next time! |
|||
|
|||
@ -0,0 +1,134 @@ |
|||
# URL-Based Localization |
|||
|
|||
In this article I'll show you how to optimize your ABP website localization with a URL parameter. URL Paths are commonly being used to change the current UI culture. This method makes our website SEO-Friendly as you structuring the URLs for multiple languages. And you can also share the link of your website with a specific language. Let's see implementing multi-language support with URL parameters in an ABP project. |
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
## Mastering Website Localization: Language Codes in URLs Explained |
|||
|
|||
Enhancing UX with URL-Based website localization is mainly done with ASP.NET Core's routing system. Also we need to automatically redirect the links without language code parameter. Our URL structures for the localization will be as following: |
|||
|
|||
- https://mywebsite.com/en/dashboard (English) |
|||
- https://mywebsite.com/tr/dashboard (Turkish) |
|||
|
|||
|
|||
|
|||
## Routing |
|||
|
|||
Before starting to explain how to do this, you can see [this commit](https://github.com/salihozkara/MultiLangRoute/commit/09e40cfd751562dec0dab890e54e0c5ca9ee256c) which implements this functionality. The routing module consists of these fundamental classes: |
|||
|
|||
### 1. **MultiLanguageSupportMetaData.cs** |
|||
|
|||
This class is used to add a language parameter to route templates. For example, it changes the `/about` route to `/language/about`. |
|||
|
|||
### 2. **MultiLanguageRedirectRequiredMetaData.cs** |
|||
|
|||
This class is used to redirect users to the correct language version. If a user visits the `/about` page and the current culture is `"tr-TR"`, this class will redirect them to `/tr-TR/about`. |
|||
|
|||
### 3. **UrlNormalizer.cs** |
|||
|
|||
This static class is used to normalize URLs by adding language information and improving performance using caching. |
|||
|
|||
```csharp |
|||
public static string NormalizeUrl(EndpointDataSource endpointDataSource, HttpContext httpContext, string url) |
|||
{ |
|||
// Normalize the URL and cache it |
|||
return Cache.GetOrAdd(url, (key) => |
|||
{ |
|||
var absoluteUrl = GetAbsoluteUrl(key); |
|||
var multiLanguageRedirectRequiredMetaData = |
|||
GetMultiLanguageRedirectRequiredMetaData(endpointDataSource, absoluteUrl); |
|||
return multiLanguageRedirectRequiredMetaData?.ReBuildUrl(httpContext, key) ?? key; |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
### 4. **MyLinkGenerator.cs** |
|||
|
|||
This class extends ASP.NET Core’s `LinkGenerator` class to automatically add language information to all generated links. |
|||
|
|||
### 5. **MyRouteDataRequestCultureProvider.cs** |
|||
|
|||
This class determines the current culture using the language parameter in the URL. For example, it extracts the `"tr-TR"` culture from the `/tr-TR/about` URL. |
|||
|
|||
### 6. **RoutingMiddleware.cs** |
|||
|
|||
This middleware processes HTTP requests and redirects users to the correct language version when necessary. |
|||
|
|||
```csharp |
|||
public override Task InvokeAsync(HttpContext context, RequestDelegate next) |
|||
{ |
|||
var endpoint = context.GetEndpoint(); |
|||
if(endpoint is not RouteEndpoint) |
|||
{ |
|||
return next(context); |
|||
} |
|||
|
|||
// Redirect if necessary |
|||
var redirectMetaData = endpoint.Metadata.GetMetadata<IRedirectMetaData>(); |
|||
if (redirectMetaData is not null) |
|||
{ |
|||
redirectMetaData.Redirect(context); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
// ... |
|||
} |
|||
``` |
|||
|
|||
|
|||
|
|||
## CultureAnchorTagHelper.cs |
|||
|
|||
This **Tag Helper** processes `<a>` tags in a Razor page and automatically adds language information to URLs if it's missing. |
|||
|
|||
```csharp |
|||
[HtmlTargetElement("a", Attributes = "href", TagStructure = TagStructure.NormalOrSelfClosing)] |
|||
public class CultureAnchorTagHelper(EndpointDataSource endpointDataSource, IHttpContextAccessor contextAccessor) |
|||
: TagHelper, ITransientDependency |
|||
{ |
|||
public override void Process(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
var href = output.Attributes["href"].Value.ToString(); |
|||
if (href != null) |
|||
{ |
|||
output.Attributes.SetAttribute("href", |
|||
UrlNormalizer.NormalizeUrl(endpointDataSource, contextAccessor.HttpContext!, href)); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
**How This Tag Helper Works:** |
|||
|
|||
- Finds all `<a href="...">` tags within your Razor pages. |
|||
- Retrieves the `href` attribute of each link. |
|||
- Uses the `UrlNormalizer.NormalizeUrl()` method to normalize the URL with the current culture information. |
|||
- Replaces the original URL with the normalized one. |
|||
|
|||
For example, if the current culture is `"tr"` and a page contains `<a href="/about">`, this Tag Helper will transform it into `<a href="/tr-TR/about">`. |
|||
|
|||
|
|||
|
|||
## Sample Project |
|||
|
|||
Salih Özkara from ABP team created a sample working project which implements URL localization. He used ABP free tier MVC template and MongoDB. You can check out the related commit which implements URL localization: |
|||
|
|||
https://github.com/salihozkara/MultiLangRoute/commit/09e40cfd751562dec0dab890e54e0c5ca9ee256c |
|||
|
|||
And full working demo is available at: |
|||
|
|||
https://github.com/salihozkara/MultiLangRoute |
|||
|
|||
You can download the demo project at: |
|||
|
|||
[UrlLocalizationSampleProject.zip](https://github.com/abpframework/abp/blob/634ff52fb07d0b1281640695dbeffccdc943ca53/docs/en/Community-Articles/2024-03-05-URL-Based-Localization/UrlLocalizationSampleProject.zip) |
|||
|
|||
|
|||
|
|||
## Summary |
|||
|
|||
This implementation extends ASP.NET Core’s routing mechanism and utilizes caching for improved performance. It ensures multilingual support by normalizing URLs, redirecting users to the appropriate language version, and automatically handling language-specific links. |
|||
|
|||
|
After Width: | Height: | Size: 588 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
@ -0,0 +1,234 @@ |
|||
# Using Outbox/Inbox Pattern for Reliable Event Handling in a Multi-Module Monolithic Application |
|||
|
|||
This article explains how to implement reliable event handling using the `Outbox/Inbox` pattern in a modular monolithic application with multiple databases. We'll use the `ModularCRM` project as an example (how that project was created is explained in [this document](https://abp.io/docs/latest/tutorials/modular-crm)). |
|||
|
|||
## Project Background |
|||
|
|||
`ModularCRM` is a monolithic application that integrates multiple ABP framework open-source modules, including: |
|||
|
|||
- `Account` |
|||
- `Identity` |
|||
- `Tenant Management` |
|||
- `Permission Management` |
|||
- `Setting Management` |
|||
- And other open-source modules |
|||
|
|||
Besides the ABP framework modules, the project contains three business modules: |
|||
|
|||
- Order module (`Ordering`), using `MongoDB` database |
|||
- Product module (`Products`), using `SQL Server` database |
|||
- Payment module (`Payment`), using `MongoDB` database |
|||
|
|||
The project configures separate database connection strings for `ModularCRM` and the three business modules in `appsettings.json`: |
|||
|
|||
```json |
|||
{ |
|||
"ConnectionStrings": { |
|||
"Default": "Server=localhost,1434;Database=ModularCrm;User Id=sa;Password=1q2w3E***;TrustServerCertificate=true", |
|||
"Products": "Server=localhost,1434;Database=ModularCrm_Products;User Id=sa;Password=1q2w3E***;TrustServerCertificate=true", |
|||
"Ordering": "mongodb://localhost:27017/ModularCrm_Ordering?replicaSet=rs0", |
|||
"Payment": "mongodb://localhost:27017/ModularCrm_Payment?replicaSet=rs0" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Business Scenario |
|||
|
|||
These modules communicate through the ABP framework's `DistributedEventBus` to implement the following business flow: |
|||
|
|||
> This is a simple example flow. Real business flows are more complex. The sample code is for demonstration purposes. |
|||
|
|||
1. Order module: Publishes `OrderPlacedEto` event when an order is placed |
|||
2. Product module: Subscribes to `OrderPlacedEto` event and reduce product stock |
|||
3. Payment module: Subscribes to `OrderPlacedEto` event, processes payment, then publishes `PaymentCompletedEto` event |
|||
4. Order module: Subscribes to `PaymentCompletedEto` event and updates order status to `Delivered` |
|||
|
|||
When implementing this flow, we need to ensure: |
|||
|
|||
- Transaction consistency between order creation and event publishing |
|||
- Transaction consistency when modules process messages |
|||
- Reliable message delivery (including persistence, confirmation, and retry mechanisms) |
|||
|
|||
Using the default implementation of the ABP framework's distributed event bus cannot meet these requirements, so we need to add a new mechanism that is also provided by the ABP Framework. |
|||
|
|||
## Outbox/Inbox Pattern Solution |
|||
|
|||
To meet these requirements, we use the `Outbox/Inbox` pattern: |
|||
|
|||
### Outbox Pattern |
|||
|
|||
- Saves distributed events with database operations in the same transaction |
|||
- Sends events to distributed message service through background jobs |
|||
- Ensures consistency between data updates and event publishing |
|||
- Prevents message loss during system failures |
|||
|
|||
### Inbox Pattern |
|||
|
|||
- First saves received distributed events to the database |
|||
- Processes events in a transactional way |
|||
- Ensures messages are processed only once by saving processed message records |
|||
- Maintains processing state for reliable handling |
|||
|
|||
> For how to enable and configure `Outbox/Inbox` in projects and modules, see: https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed#outbox-inbox-for-transactional-events |
|||
|
|||
### Module Configuration |
|||
|
|||
Each module needs to configure separate `Outbox/Inbox`. Since it's a monolithic application, all message processing classes are in the same project, so we need to configure `Outbox/Inbox` for each module with `Selector/EventSelector` to ensure that the module only sends and receives the messages it cares about, avoiding message duplication processing. |
|||
|
|||
**ModularCRM Main Application Configuration** |
|||
|
|||
It will send and receive messages from all ABP framework open-source modules. |
|||
|
|||
```csharp |
|||
// This selector will match all abp built-in modules and the current module. |
|||
Func<Type, bool> abpModuleSelector = type => type.Namespace != null && (type.Namespace.StartsWith("Volo.") || type.Assembly == typeof(ModularCrmModule).Assembly); |
|||
|
|||
Configure<AbpDistributedEventBusOptions>(options => |
|||
{ |
|||
options.Inboxes.Configure("ModularCrm", config => |
|||
{ |
|||
config.UseDbContext<ModularCrmDbContext>(); |
|||
config.EventSelector = abpModuleSelector; |
|||
config.HandlerSelector = abpModuleSelector; |
|||
}); |
|||
|
|||
options.Outboxes.Configure("ModularCrm", config => |
|||
{ |
|||
config.UseDbContext<ModularCrmDbContext>(); |
|||
config.Selector = abpModuleSelector; |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
**Order Module Configuration** |
|||
|
|||
It only sends `OrderPlacedEto` events and receives `PaymentCompletedEto` events and executes `OrderPaymentCompletedEventHandler`. |
|||
|
|||
```csharp |
|||
Configure<AbpDistributedEventBusOptions>(options => |
|||
{ |
|||
options.Inboxes.Configure(OrderingDbProperties.ConnectionStringName, config => |
|||
{ |
|||
config.UseMongoDbContext<IOrderingDbContext>(); |
|||
config.EventSelector = type => type == typeof(PaymentCompletedEto); |
|||
config.HandlerSelector = type => type == typeof(OrderPaymentCompletedEventHandler); |
|||
}); |
|||
|
|||
options.Outboxes.Configure(OrderingDbProperties.ConnectionStringName, config => |
|||
{ |
|||
config.UseMongoDbContext<IOrderingDbContext>(); |
|||
config.Selector = type => type == typeof(OrderPlacedEto); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
> Here, the `EventSelector` and `HandlerSelector` checks only a single type. If you have multiple events and event handlers, you can check the given type if it is included in an array of types. |
|||
|
|||
**Product Module Configuration** |
|||
|
|||
It only receives `EntityCreatedEto<UserEto>` and `OrderPlacedEto` events and executes `ProductsOrderPlacedEventHandler` and `ProductsUserCreatedEventHandler`. It does not send any events now. |
|||
|
|||
```csharp |
|||
Configure<AbpDistributedEventBusOptions>(options => |
|||
{ |
|||
options.Inboxes.Configure(ProductsDbProperties.ConnectionStringName, config => |
|||
{ |
|||
config.UseDbContext<IProductsDbContext>(); |
|||
config.EventSelector = type => type == typeof(EntityCreatedEto<UserEto>) || type == typeof(OrderPlacedEto); |
|||
config.HandlerSelector = type => type == typeof(ProductsOrderPlacedEventHandler) || type == typeof(ProductsUserCreatedEventHandler); |
|||
}); |
|||
|
|||
// Outboxes are not used in this module |
|||
options.Outboxes.Configure(ProductsDbProperties.ConnectionStringName, config => |
|||
{ |
|||
config.UseDbContext<IProductsDbContext>(); |
|||
config.Selector = type => false; |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
**Payment Module Configuration** |
|||
|
|||
It only sends `PaymentCompletedEto` events and receives `OrderPlacedEto` events and executes `PaymentOrderPlacedEventHandler`. |
|||
|
|||
```csharp |
|||
Configure<AbpDistributedEventBusOptions>(options => |
|||
{ |
|||
options.Inboxes.Configure(PaymentDbProperties.ConnectionStringName, config => |
|||
{ |
|||
config.UseMongoDbContext<IPaymentMongoDbContext>(); |
|||
config.EventSelector = type => type == typeof(OrderPlacedEto); |
|||
config.HandlerSelector = type => type == typeof(PaymentOrderPlacedEventHandler); |
|||
}); |
|||
|
|||
options.Outboxes.Configure(PaymentDbProperties.ConnectionStringName, config => |
|||
{ |
|||
config.UseMongoDbContext<IPaymentMongoDbContext>(); |
|||
config.Selector = type => type == typeof(PaymentCompletedEto); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
## Running ModularCRM Simulation Business Flow |
|||
|
|||
1. Run the following command in the `ModularCrm` directory: |
|||
|
|||
``` |
|||
# Start SQL Server and MongoDB databases in Docker |
|||
docker-compose up -d |
|||
|
|||
# Restore and install project npm dependencies |
|||
abp install-lib |
|||
|
|||
# Migrate databases |
|||
dotnet run --project ModularCrm --migrate-database |
|||
|
|||
# Start the application |
|||
dotnet run --project ModularCrm |
|||
``` |
|||
|
|||
2. Navigate to `https://localhost:44303/` to view the application homepage |
|||
|
|||
 |
|||
|
|||
3. Enter a customer name and select a product, then submit an order. After a moment, refresh the page to see the order, product, and payment information. |
|||
|
|||
 |
|||
|
|||
Application logs display the complete processing flow: |
|||
|
|||
``` |
|||
[Ordering Module] Order created: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9, CustomerName: john |
|||
|
|||
[Products Module] OrderPlacedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, CustomerName: john, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9 |
|||
[Products Module] Stock count decreased for ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9 |
|||
|
|||
[Payment Module] OrderPlacedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, CustomerName: john, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9 |
|||
[Payment Module] Payment processing completed for OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88 |
|||
|
|||
[Ordering Module] PaymentCompletedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, PaymentId: d0a41ead-ee0f-714c-e254-3a1834504d65, PaymentMethod: CreditCard, PaymentAmount: ModularCrm.Payment.Payment.PaymentCompletedEto |
|||
[Ordering Module] Order state updated to Delivered for OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88 |
|||
``` |
|||
|
|||
In addition, when a new user registers, the product module will also receive the `EntityCreatedEto<UserEto>` event, and we will send an email to the new user, just to demonstrate the `Outbox/Inbox Selector` mechanism. |
|||
|
|||
``` |
|||
[Products Module] UserCreated event received: UserId: "9a1f2bd0-5b28-210a-9e56-3a18344d310a", UserName: admin |
|||
[Products Module] Sending a popular products email to admin@abp.io... |
|||
``` |
|||
|
|||
## Summary |
|||
|
|||
By introducing the `Outbox/Inbox` pattern, we have achieved: |
|||
|
|||
1. Transactional message sending and receiving |
|||
2. Reliable message processing mechanism |
|||
3. Modular event processing in a multi-database environment |
|||
|
|||
ModularCRM project not only implements reliable message processing but also demonstrates how to handle multi-database scenarios gracefully in a monolithic application. Project source code: https://github.com/abpframework/abp-samples/tree/master/ModularCrm-OutboxInbox-Pattern |
|||
|
|||
## Reference |
|||
|
|||
- [Outbox/Inbox for transactional events](https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed#outbox-inbox-for-transactional-events) |
|||
- [ConnectionStrings](https://abp.io/docs/latest/framework/fundamentals/connection-strings) |
|||
- [ABP Studio: Single Layer Solution Template](https://abp.io/docs/latest/solution-templates/single-layer-web-application) |
|||
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 250 KiB |
@ -0,0 +1,28 @@ |
|||
We are excited to share some fantastic news with our community! We are proud to announce that ABP.IO is going to be at the BASTA! Conference on March 03-07, 2025 in Frankfurt and İsmail Çağdaş from our dev team is going to be a speaker on March 04! |
|||
|
|||
#### **About BASTA!**
|
|||
For those who don’t know, BASTA! is the leading independent conference for Microsoft technologies in the German-speaking world. For over 20 years, it has been setting standards in the areas of C#, .NET and cloud and web technologies and is considered a must-attend event for Microsoft, cloud, web developers and key players in the software industry. BASTA! is a conference for developers and IT professionals who want to stay up to date with the latest technologies. |
|||
|
|||
#### **ABP at [BASTA! Mainz 2023](https://abp.io/blog/BASTA-Mainz-2023-What-a-Blast-in-Germany)**
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
#####
|
|||
#### **What to Expect at [BASTA! 2025](https://basta.net/frankfurt-en/)**
|
|||
The most exciting part is İsmail Çağdaş from the ABP developer team will be speaking at the conference on March 04 about the concepts of monoliths and microservices will be briefly explored, along with how modular monoliths bring together the advantages of these two architectures. Using the ABP Framework as an example, the session will highlight its modularity features and demonstrate how it can assist in creating and developing a modular monolith application. Finally, best practices for developing modular monoliths will be discussed, showing how these practices can pave the way for transitioning to a microservice-based architecture when necessary. |
|||
|
|||
If you want to find out more information about İsmail Çağdaş's session, [check here](https://basta.net/microservices-apis/modular-monoliths-architecture-abp/?loc=ffm): |
|||
|
|||
#### **Connect with Us**
|
|||
We have exciting raffles and surprises planned at our booth and look forward to sharing more information about our solutions with you there. |
|||
|
|||
#### **Join Us Online**
|
|||
Don't worry if you can't join us in person, our online booth is going to be there for you! The Expo of the online version of BASTA! is open for the main conference days. |
|||
|
|||
Tuesday, March 4, 2025: 9:00 am – approx. 6:00 pm |
|||
|
|||
Wednesday, March 5, 2025: 9:00 am – approx. 6:00 pm |
|||
|
|||
Thursday, March 6, 2025: 9:00 am – approx. 5:45 pm |
|||
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,39 @@ |
|||
 |
|||
|
|||
Our team had an amazing time at BASTA\! Frankfurt 2025, held from March 3 to 8 at the Frankfurt am Main Marriott Hotel. As a sponsor for this major conference for asp.net web developers, we were thrilled to connect with so many talented participants. |
|||
|
|||
**Event Highlights** |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
The conference hosted many talented developers as speakers. One of them was İsmail Çağdaş who’s a lead developer from ABP team who talked about the concepts of monoliths and microservices. He explained how modular monoliths combine the strengths of these two architectures, using the ABP Framework as an example to showcase its modularity features and demonstrate how to build and develop a modular monolith application. |
|||
|
|||
**ABP’S Presence** |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
At our booth we displayed the latest features of the ABP Framework and gathered valuable feedback from especially dotnet developers. So many attendees showed interest in ABP which was very exciting. It was great engaging with so many participants who wanted to learn more about how ABP provides the infrastructure and tools to create business solutions. |
|||
|
|||
We're also very grateful to our booth neighbor, Xceed and it was a lot of fun connecting with them during the conference. For those who don’t know, Xceed provides comprehensive UI components that allow developers to focus on innovation and their business requirements. |
|||
|
|||
**Networking and Community Engagement** |
|||
|
|||
We organized two raffles during the event, where attendees had the chance to win 2 great prizes. One attendee won a LEGO set and another won an Amazon Kindle. We were happy to see many people attending our raffles, it definitely made this event more fun. Congratulations to the winners and thanks to those who participated\! |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
**Looking Ahead** |
|||
|
|||
BASTA\! Frankfurt 2025 strengthened our commitment to the developer community. We want to continue our support for asp.net core developers, helping them create asp.net applications and optimize their workflows for web applications. |
|||
|
|||
**Gratitude and Future Events** |
|||
|
|||
Thank you to the organizers, speakers, and attendees for making BASTA\! Frankfurt 2025 such a fantastic experience. We look forward to future events and continued contributions to the net framework developers. |
|||
|
|||
We look forward to sharing more updates with you soon. We hope to see you at our next event\! |
|||
|
After Width: | Height: | Size: 4.7 KiB |
@ -0,0 +1,324 @@ |
|||
# Using Vue components in a Razor Pages ABP Application |
|||
|
|||
In modern web development, integrating dynamic front-end frameworks with server-side technologies has become increasingly essential for creating responsive and interactive applications. This article explores how to effectively use Vue components within Razor Pages in an ABP Framework application. We will delve into the process of consuming endpoints through ABP Client Proxies, leveraging ABP's powerful localization features to enhance user experience, and implementing ABP permissions to ensure secure access control. By the end of this guide, you will have a comprehensive understanding of how to seamlessly blend Vue.js with Razor Pages, empowering you to build robust and user-friendly applications. |
|||
|
|||
This article won't use any SPA approach. The goal of this article is to use Razor Pages with simple Vue components to eliminate jQuery while developing MVC application. |
|||
|
|||
> **🎉 Also video version is available!** |
|||
> |
|||
> [Watch on YouTube Now!](https://youtu.be/sZ8iSMovHZs?si=GynuJjsLEI1p2g6w) |
|||
|
|||
## Creating the Solution |
|||
|
|||
Let's create a simple TODO list application to demonstrate how to use Vue components in Razor Pages. I'll build a really simple backend without a connection to a database for demonstration purposes. We will focus on the frontend part. |
|||
|
|||
- Creating a solution with ABP CLI: |
|||
|
|||
```bash |
|||
abp new MyTodoApp -t app-nolayers -csf |
|||
``` |
|||
|
|||
## Configure Vue |
|||
|
|||
We need to add the `@abp/vue` package to the project to use Vue components. |
|||
|
|||
```bash |
|||
npm install @abp/vue |
|||
``` |
|||
|
|||
- Install client libraries by using ABP CLI: |
|||
|
|||
```bash |
|||
abp install-libs |
|||
``` |
|||
|
|||
As a last step, we need to configure our bundle in the `ConfigureBundles` method in the `MyTodoAppModule.cs` file: |
|||
|
|||
```csharp |
|||
private void ConfigureBundles() |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
// ... |
|||
|
|||
options.ScriptBundles.Configure( |
|||
// Or BasicThemeBundles.Scripts.Global |
|||
// Or LeptonXLiteThemeBundles.Scripts.Global |
|||
// 👇 Depends on the theme you are using |
|||
LeptonXThemeBundles.Scripts.Global, |
|||
bundle => |
|||
{ |
|||
bundle.AddFiles("/global-scripts.js"); |
|||
// 👇 Make sure to add this line |
|||
bundle.AddContributors(typeof(VueScriptContributor)); |
|||
} |
|||
); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
> If your IDE doesn't recognize the namespace of the `VueScriptContributor`, you can add it manually: |
|||
> |
|||
> ```csharp |
|||
> using Volo.Abp.AspNetCore.Mvc.UI.Packages.Vue; |
|||
> ``` |
|||
|
|||
Now we're ready to use Vue components in our Razor Pages. |
|||
|
|||
## Creating a Vue Component |
|||
|
|||
Let's create a simple Vue component to display the TODO list. |
|||
|
|||
### Passing a simple message to the component |
|||
|
|||
- Remove existing HTML codes in `Index.cshtml` and replace with the following code: |
|||
|
|||
```html |
|||
<div id="vue-app"> |
|||
<message-component :message="'Welcome, @CurrentUser.UserName !'"></todo-component> |
|||
</div> |
|||
``` |
|||
|
|||
- Navigate to the `Index.cshtml.js` file and add the following code: |
|||
```js |
|||
Vue.component('message-component', { |
|||
template: '<div>Hello, {{ message }}</div>', |
|||
props: ['message'] |
|||
}); |
|||
|
|||
new Vue({ |
|||
el: '#vue-app' |
|||
}); |
|||
``` |
|||
|
|||
Run the application and you should see the following output: |
|||
|
|||
 |
|||
|
|||
> _Hard refresh might be required to see the component since we added a new vue js file to the bundle._ |
|||
> |
|||
> If still you can't see the component, please check the browser console for any errors. |
|||
|
|||
### Interacting with the component |
|||
|
|||
Let's add a button to the component to interact with the component. |
|||
|
|||
- Add another component in the `Index.cshtml` file: |
|||
|
|||
```html |
|||
<div id="vue-app"> |
|||
<message-component :message="'Welcome, @CurrentUser.UserName !'"></message-component> |
|||
<counter-component></counter-component> |
|||
</div> |
|||
``` |
|||
|
|||
```js |
|||
Vue.component('counter-component', { |
|||
template:` |
|||
<div class="card"> |
|||
<div class="card-body"> |
|||
<p>Count: {{ count }}</p> |
|||
<button class="btn btn-primary" @click="increment">Increment</button> |
|||
</div> |
|||
</div> |
|||
`, |
|||
data: function () { |
|||
return { |
|||
count: 0 |
|||
}; |
|||
}, |
|||
methods: { |
|||
increment: function () { |
|||
this.count++; |
|||
} |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
> _Do not replicate `new Vue({})` code block in the file. It's already in the `Index.cshtml.js` file. Keep it at the bottom of the file as it is._ |
|||
|
|||
Run the application and you should see the following output: |
|||
|
|||
 |
|||
|
|||
|
|||
## Using ABP Client Proxy, Authorization and Localization |
|||
|
|||
|
|||
### Building the backend |
|||
Before we go, let's build our backend to use in the component. |
|||
|
|||
- Creating a simple Application Service: |
|||
|
|||
```csharp |
|||
public class TodoAppService : MyTodoAppAppService, ITodoAppService |
|||
{ |
|||
public static List<TodoItem> Items { get; } = new List<TodoItem>(); |
|||
|
|||
[Authorize("Todo.Create")] |
|||
public async Task<TodoItem> AddTodoItemAsync(TodoItem input) |
|||
{ |
|||
Items.Add(input); |
|||
return input; |
|||
} |
|||
|
|||
[Authorize("Todo")] |
|||
public async Task<List<TodoItem>> GetAllAsync() |
|||
{ |
|||
await Task.Delay(1500); |
|||
return Items; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
- `TodoItem.cs` |
|||
|
|||
```csharp |
|||
public class TodoItem |
|||
{ |
|||
public string Description { get; set; } |
|||
public bool IsDone { get; set; } |
|||
} |
|||
``` |
|||
|
|||
- `ITodoAppService.cs` |
|||
|
|||
```csharp |
|||
public interface ITodoAppService |
|||
{ |
|||
Task<List<TodoItem>> GetAllAsync(); |
|||
Task<TodoItem> AddTodoItemAsync(TodoItem input); |
|||
} |
|||
``` |
|||
|
|||
- Run the application and if you can see the following client proxy in the browser console, you're ready to go: |
|||
|
|||
 |
|||
|
|||
> [!NOTE] |
|||
> If you can't see the client proxy in the browser console, please check the [Dynamic JavaScript Proxies](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/dynamic-javascript-proxies) to learn how to enable it. |
|||
|
|||
- Add a new permission in the `MyTodoAppPermissionDefinitionProvider.cs` file: |
|||
```csharp |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup(MyTodoAppPermissions.GroupName); |
|||
|
|||
var todo = myGroup.AddPermission("Todo"); |
|||
todo.AddChild("Todo.Create"); |
|||
} |
|||
``` |
|||
> _I go without localization or constants for simplicity._ |
|||
|
|||
- Add a localization key in the `en.json` file: |
|||
|
|||
```json |
|||
{ |
|||
"TodoItems": "Todo Items Localized" |
|||
} |
|||
``` |
|||
|
|||
### Building the Vue Component: Using ABP Localization, Authorization and Client Proxy |
|||
|
|||
Since the component it directly loaded into the page, we can access the `abp` object on the page. |
|||
|
|||
So we can use: |
|||
|
|||
- `abp.localization.localize()` to localize a string. |
|||
- `abp.auth.isGranted()` to check the authorization. |
|||
- `myTodoApp.todo.getAll()` and `myTodoApp.todo.addTodoItem` to call the Application Service. |
|||
|
|||
inside **Vue Component** code. |
|||
|
|||
- Let's add another component named `todo-component` and usee all the **ABP Features** in it. |
|||
|
|||
```html |
|||
<div id="vue-app"> |
|||
<!-- ... --> |
|||
<todo-component></todo-component> |
|||
</div> |
|||
``` |
|||
|
|||
- Implement the `todo-component` in `Index.cshtml.js` file: |
|||
|
|||
```js |
|||
Vue.component('todo-component', { |
|||
template: ` |
|||
<div class="card" v-if="abp.auth.isGranted('Todo')"> |
|||
<div class="card-header border-bottom"> |
|||
<h3>{{ abp.localization.localize('TodoItems') }}</h3> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div v-if="isBusy" class="w-100 text-center"> |
|||
<div class="spinner-border" role="status"> |
|||
<span class="visually-hidden">Loading...</span> |
|||
</div> |
|||
</div> |
|||
<ul v-else-if="todos.length > 0" class="list-group"> |
|||
<li class="list-group-item" v-for="item in todos" :key="item.description"> |
|||
<input class="form-check-input" type="checkbox" v-model="item.isDone"> |
|||
<label class="form-check-label">{{ item.description }}</label> |
|||
</li> |
|||
</ul> |
|||
<p v-else>No todos yet</p> |
|||
</div> |
|||
<div v-if="abp.auth.isGranted('Todo.Create')" class="card-footer d-flex flex-column gap-2 border-top pt-2"> |
|||
<input class="form-control" type="text" v-model="newTodo.description" placeholder="Add a new todo"> |
|||
<div class="form-check"> |
|||
<input class="form-check-input" type="checkbox" v-model="newTodo.isDone" id="isDone"> |
|||
<label class="form-check-label" for="isDone">Is Done</label> |
|||
</div> |
|||
|
|||
<button class="btn btn-primary" @click="addTodo">Add</button> |
|||
</div> |
|||
</div> |
|||
`, |
|||
data: function () { |
|||
return { |
|||
newTodo: { |
|||
description: '', |
|||
isDone: false |
|||
}, |
|||
isBusy: false, |
|||
todos: [] |
|||
}; |
|||
}, |
|||
methods: { |
|||
addTodo() { |
|||
myTodoApp.todo.addTodoItem(this.newTodo); |
|||
this.newTodo = { description: '', isDone: false }; |
|||
this.todos.push(this.newTodo); |
|||
|
|||
// Preferrable, you can load entire list of todos again. |
|||
// this.loadTodos(); |
|||
}, |
|||
async loadTodos() { |
|||
if (!abp.auth.isGranted('Todo')) { |
|||
return; |
|||
} |
|||
this.isBusy = true; |
|||
this.todos = await myTodoApp.todo.getAll(); |
|||
this.isBusy = false; |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.loadTodos(); |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
And see the result: |
|||
|
|||
 |
|||
|
|||
|
|||
Since we use `abp.auth.isGranted()` to check the authorization, we can see the component only if we have the permission. |
|||
|
|||
Whenever you remove `Todo.Create` permission, you can see the component is not rendered. |
|||
|
|||
 |
|||
|
|||
|
|||
You won't see the card footer: |
|||
|
|||
 |
|||
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 337 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 212 KiB |
@ -0,0 +1,248 @@ |
|||
# Understanding the Embedded Files in ABP Framework |
|||
|
|||
Embedded Files functionality in .NET applications allows external files (like configuration files, images, etc.) to be directly embedded into assemblies (.exe or .dll). This simplifies deployment, prevents file loss or tampering, improves security and performance, and reduces path and dependency management issues. Through embedded resources, programs can access these files more conveniently without additional file operations. |
|||
|
|||
## Embedding Files in Your Project |
|||
|
|||
We embed `Volo\Abp\MyModule\Localization\*.json` files into the assembly in our `MyModule.csproj`. |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net9.0</TargetFramework> |
|||
<OutputType>Exe</OutputType> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> |
|||
<PackageReference Include="Volo.Abp.VirtualFileSystem" Version="9.0.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="Volo\Abp\MyModule\Localization\*.json" /> |
|||
<EmbeddedResource Include="Volo\Abp\MyModule\Localization\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
``` |
|||
|
|||
If we check the `en.json` file in our IDE, we'll see it's embedded in the assembly. |
|||
|
|||
 |
|||
|
|||
When we decompile the built `MyModule.dll` file, we can also see the `en.json` file. |
|||
|
|||
 |
|||
|
|||
## Accessing Embedded Files in Code |
|||
|
|||
```csharp |
|||
public class Program |
|||
{ |
|||
public static async Task<int> Main(string[] args) |
|||
{ |
|||
var embeddedFiles = typeof(Program).Assembly.GetManifestResourceNames(); |
|||
foreach (var embeddedFile in embeddedFiles) |
|||
{ |
|||
Console.WriteLine(embeddedFile); |
|||
var fileStream = typeof(Program).Assembly.GetManifestResourceStream(embeddedFile); |
|||
if (fileStream != null) |
|||
{ |
|||
using var reader = new System.IO.StreamReader(fileStream); |
|||
var content = await reader.ReadToEndAsync(); |
|||
Console.WriteLine(content); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
This code will output the embedded file names and their contents. |
|||
|
|||
``` |
|||
MyModule.Volo.Abp.MyModule.Localization.en.json |
|||
|
|||
{ |
|||
"key":"value" |
|||
} |
|||
``` |
|||
|
|||
## Integrating with ABP Virtual File System |
|||
|
|||
The ABP Virtual File System makes it possible to manage files that don't physically exist on the file system (disk). It's mainly used to embed (js, css, image..) files into assemblies and use them like physical files at runtime. |
|||
|
|||
The following code shows how to add embedded files from the current application assembly to the ABP virtual file system: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpVirtualFileSystemModule))] |
|||
public class MyModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<MyModule>(); |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ABP creates an `AbpEmbeddedFileProvider` to access the embedded files. |
|||
|
|||
The full name of `en.json` is `MyModule.Volo.Abp.MyModule.Localization.en.json`. Without directory information, ABP uses `.` to split and assume directory information. This creates the following directory structure in the virtual file system: |
|||
|
|||
``` |
|||
[Dir] [/MyModule] |
|||
[Dir] [/MyModule/Volo] |
|||
[Dir] [/MyModule/Volo/Abp] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule/Localization] |
|||
[File] [/MyModule/Volo/Abp/MyModule/Localization/en.json] |
|||
``` |
|||
|
|||
Now you can inject `IVirtualFileProvider` to access embedded files using the directory/file structure above. |
|||
|
|||
## Manifest Embedded File Provider |
|||
|
|||
You might have noticed that using `.` to split and assume directory information can cause confusion if filenames contain dots. |
|||
|
|||
For example, if your filename is `zh.hans.json`, ABP will generate the following directory structure, which isn't what we want: |
|||
|
|||
``` |
|||
[Dir] [/MyModule] |
|||
[Dir] [/MyModule/Volo] |
|||
[Dir] [/MyModule/Volo/Abp] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule/Localization] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule/Localization/zh] |
|||
[File] [/MyModule/Volo/Abp/MyModule/Localization/zh/hans.json] |
|||
``` |
|||
|
|||
Microsoft provides the `Microsoft.Extensions.FileProviders.Manifest` library to solve this problem. |
|||
|
|||
We need to add this package dependency and set `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` in our project: |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net9.0</TargetFramework> |
|||
<OutputType>Exe</OutputType> |
|||
<Nullable>enable</Nullable> |
|||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> |
|||
<PackageReference Include="Volo.Abp.VirtualFileSystem" Version="9.0.0" /> |
|||
<PackageReference Include="Microsoft.Extensions.FileProviders.Manifest" Version="9.0.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="Volo\Abp\MyModule\Localization\*.json" /> |
|||
<EmbeddedResource Include="Volo\Abp\MyModule\Localization\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
``` |
|||
|
|||
After rebuilding the project, when we decompile `MyModule.dll`, we'll see an additional `Microsoft.Extensions.FileProviders.Embedded.Manifest.xml` file. |
|||
|
|||
 |
|||
|
|||
This manifest file stores all the directory and file information of embedded resources. When ABP finds this file, it will use `ManifestEmbeddedFileProvider` instead of `AbpEmbeddedFileProvider` to access embedded files: |
|||
|
|||
```xml |
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?> |
|||
<Manifest> |
|||
<ManifestVersion>1.0</ManifestVersion> |
|||
<FileSystem> |
|||
<File Name="Microsoft.Extensions.FileProviders.Embedded.Manifest.xml"> |
|||
<ResourcePath>Microsoft.Extensions.FileProviders.Embedded.Manifest.xml</ResourcePath> |
|||
</File> |
|||
<Directory Name="Volo"> |
|||
<Directory Name="Abp"> |
|||
<Directory Name="MyModule"> |
|||
<Directory Name="Localization"> |
|||
<File Name="zh.hans.json"> |
|||
<ResourcePath>MyModule.Volo.Abp.MyModule.Localization.zh.hans.json</ResourcePath> |
|||
</File> |
|||
</Directory> |
|||
</Directory> |
|||
</Directory> |
|||
</Directory> |
|||
</FileSystem> |
|||
</Manifest> |
|||
``` |
|||
|
|||
## Parameters of AddEmbedded Method |
|||
|
|||
The `AddEmbedded` method can take two parameters: |
|||
|
|||
### baseNamespace |
|||
|
|||
This may only be needed if you haven't used the `Manifest Embedded File Provider` and your project's `root namespace` isn't empty. In this case, set your root namespace here. |
|||
|
|||
The `root namespace` is your project's name by default. You can change it or set it to empty in the `csproj` file. |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<RootNamespace>MyModule</RootNamespace> |
|||
</PropertyGroup> |
|||
</Project> |
|||
``` |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<RootNamespace></RootNamespace> |
|||
</PropertyGroup> |
|||
</Project> |
|||
``` |
|||
|
|||
```csharp |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<MyModule>(baseNamespace: "MyModule"); |
|||
}); |
|||
``` |
|||
|
|||
``` |
|||
[Dir] [/Volo] |
|||
[Dir] [/Volo/Abp] |
|||
[Dir] [/Volo/Abp/MyModule] |
|||
[Dir] [/Volo/Abp/MyModule/Localization] |
|||
[File] [/Volo/Abp/MyModule/Localization/en.json] |
|||
``` |
|||
|
|||
### baseFolder |
|||
|
|||
If you don't want to expose all embedded files in the project, but only want to expose a specific folder (and sub folders/files), you can set the base folder relative to your project root folder. |
|||
|
|||
> baseFolder is only effective when using `Manifest Embedded File Provider`. |
|||
|
|||
You can set the `baseFolder` parameter to `/Volo/Abp/MyModule`, resulting in this directory structure: |
|||
|
|||
```csharp |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<MyModule>(baseFolder: "/Volo/Abp/MyModule"); |
|||
}); |
|||
``` |
|||
|
|||
``` |
|||
[Dir] [Localization] |
|||
[File] [Localization/en.json] |
|||
``` |
|||
|
|||
## Summary |
|||
|
|||
We recommend using the `Manifest Embedded File Provider` in your projects and libraries. Hope this article has been helpful. |
|||
|
|||
## References |
|||
|
|||
[ABP Virtual File System](https://abp.io/docs/latest/framework/infrastructure/virtual-file-system) |
|||
|
|||
[Manifest Embedded File Provider](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/file-providers#manifest-embedded-file-provider) |
|||
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 485 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 332 KiB |
@ -1,5 +1,36 @@ |
|||
# KB#0003: Can not login with the admin user |
|||
# KB#0003: Cannot login with the admin user |
|||
|
|||
* Try username `admin` and Password `1q2w3E*`. |
|||
* Try to migrate database. If you have a `DbMigrator` application in your solution, use it. It will seed initial data and create the admin user for you. |
|||
* If not works, read the README.MD file in your solution, or check the [Getting Started](https://abp.io/docs/latest/get-started) document. |
|||
## Use the Correct Username and Password |
|||
|
|||
You may have entered the wrong password. The username is `admin`, and the password is `1q2w3E*`. Note that the password is case-sensitive. |
|||
|
|||
## Forgot to Seed Initial Data |
|||
|
|||
You may need to add migrations and update the database using the EF Core CLI. If your solution includes a `DbMigrator` application, you must run the `DbMigrator` application to seed the initial data. |
|||
|
|||
If your project does not include a `DbMigrator` application, there might be a `migrate-database.ps1` script available. You can use it to migrate and seed the initial data. |
|||
|
|||
> The no-layer application typically support a `--migrate-database` option for migrating and seeding initial data. |
|||
|
|||
> Example: |
|||
> ```bash |
|||
> dotnet run --migrate-database |
|||
> ``` |
|||
|
|||
## Tenant Admin User |
|||
|
|||
If you cannot log in as a tenant admin user, ensure the tenant database is created and seeded, Use the password that was set during tenant creation. |
|||
|
|||
> The tenant seeding process is handled by the template project. If it is not completed, please check the `Logs` file for any error logs. |
|||
|
|||
## Check the `AbpUsers` Table |
|||
|
|||
If you have performed migration and seeded the initial data, check the `AbpUsers` table in the database. Ensure that the user record exists. If your tenant has a separate database, check the tenant database as well. |
|||
|
|||
Passwords are stored in hashed format, not plain text. If you suspect the password is incorrect, you can delete the user record and re-seed the initial data using the `DbMigrator` application or the `migrate-database.ps1` script. |
|||
|
|||
## Other Issues |
|||
|
|||
If the issue persists, refer to the `README.MD` file in your solution or consult the [Getting Started](https://abp.io/docs/latest/get-started) documentation. |
|||
|
|||
Feel free to create an issue in the [ABP GitHub repository](https://github.com/abpframework/abp/issues/new/choose) or contact [ABP Commercial Support](https://abp.io/support/questions/New) for assistance. |
|||
|
|||
@ -0,0 +1,121 @@ |
|||
# Layered Solution: Health Check Configuration |
|||
|
|||
```json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "CORS Configuration", |
|||
"Path": "solution-templates/single-layer-web-application/cors-configuration" |
|||
}, |
|||
"Next": { |
|||
"Name": "Helm Charts and Kubernetes", |
|||
"Path": "solution-templates/layered-web-application/helm-charts-and-kubernetes" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Health Check is a feature that allows applications to monitor their health and diagnose potential issues. The layered solution template comes with pre-configured Health Check system. |
|||
|
|||
In the layered solution template, Health Check configuration is applied in the following cases: |
|||
|
|||
- When [MVC](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#mvc) is selected as the web application type. |
|||
- When [Blazor Server](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#blazor-server) is selected as the web application type. |
|||
- When [Blazor WebAssembly](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#blazor-webassembly) is selected as the web application type (configured at the backend). |
|||
- When [Blazor WebApp](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#blazor-webapp) is selected as the web application type (configured at the backend). |
|||
- When [Angular](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#angular) is selected as the web application type (configured at the backend). |
|||
- When [No UI](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#no-ui) is selected as the web application type (configured at the backend). |
|||
|
|||
### Configuration in `HealthChecksBuilderExtensions.cs` |
|||
|
|||
Health Checks are configured in the `HealthChecksBuilderExtensions` class. This class extends `IServiceCollection` to register health check services and configure health check UI endpoints. |
|||
|
|||
#### Default Configuration |
|||
|
|||
The default setup is as follows: |
|||
|
|||
```csharp |
|||
using HealthChecks.UI.Client; |
|||
using Microsoft.AspNetCore.Diagnostics.HealthChecks; |
|||
|
|||
namespace MyCompanyName.MyProjectName.HealthChecks; |
|||
|
|||
public static class HealthChecksBuilderExtensions |
|||
{ |
|||
public static void AddMyProjectNameHealthChecks(this IServiceCollection services) |
|||
{ |
|||
// Add your health checks here |
|||
var healthChecksBuilder = services.AddHealthChecks(); |
|||
healthChecksBuilder.AddCheck<MyProjectNameDatabaseCheck>("MyProjectName DbContext Check", tags: new string[] { "database" }); |
|||
|
|||
// Read configuration for health check URL |
|||
var configuration = services.GetConfiguration(); |
|||
var healthCheckUrl = configuration["App:HealthCheckUrl"] ?? "/health-status"; |
|||
|
|||
services.ConfigureHealthCheckEndpoint(healthCheckUrl); |
|||
|
|||
// Configure HealthChecks UI |
|||
var healthChecksUiBuilder = services.AddHealthChecksUI(settings => |
|||
{ |
|||
settings.AddHealthCheckEndpoint("MyProjectName Health Status", healthCheckUrl); |
|||
}); |
|||
|
|||
// Set HealthCheck UI storage |
|||
healthChecksUiBuilder.AddInMemoryStorage(); |
|||
|
|||
services.MapHealthChecksUiEndpoints(options => |
|||
{ |
|||
options.UIPath = "/health-ui"; |
|||
options.ApiPath = "/health-api"; |
|||
}); |
|||
} |
|||
|
|||
private static IServiceCollection ConfigureHealthCheckEndpoint(this IServiceCollection services, string path) |
|||
{ |
|||
.... |
|||
} |
|||
|
|||
private static IServiceCollection MapHealthChecksUiEndpoints(this IServiceCollection services, Action<global::HealthChecks.UI.Configuration.Options>? setupOption = null) |
|||
{ |
|||
.... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Database Health Check Implementation |
|||
|
|||
The `MyProjectNameDatabaseCheck` class is a custom implementation of a health check that verifies database connectivity using `IIdentityRoleRepository`. |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Diagnostics.HealthChecks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace MyCompanyName.MyProjectName.HealthChecks; |
|||
|
|||
public class MyProjectNameDatabaseCheck : IHealthCheck, ITransientDependency |
|||
{ |
|||
protected readonly IIdentityRoleRepository IdentityRoleRepository; |
|||
|
|||
public MyProjectNameDatabaseCheck(IIdentityRoleRepository identityRoleRepository) |
|||
{ |
|||
IdentityRoleRepository = identityRoleRepository; |
|||
} |
|||
|
|||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) |
|||
{ |
|||
try |
|||
{ |
|||
await IdentityRoleRepository.GetListAsync(sorting: nameof(IdentityRole.Id), maxResultCount: 1, cancellationToken: cancellationToken); |
|||
return HealthCheckResult.Healthy($"Could connect to database and get record."); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return HealthCheckResult.Unhealthy($"Error when trying to get database record. ", e); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
@ -0,0 +1,110 @@ |
|||
# Microservice Solution: Health Check Configuration |
|||
|
|||
```json |
|||
//[doc-nav] |
|||
{ |
|||
"Next": { |
|||
"Name": "Communication in the Microservice solution", |
|||
"Path": "solution-templates/microservice/communication" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Health Check is a feature that allows applications to monitor their health and diagnose potential issues. The Microservice solution template comes with pre-configured Health Check system. |
|||
|
|||
In the Microservice solution template, Health Check configuration is applied in all the services, gateways and UI applications (except Blazor Wasm & Blazor WebApp applications UI applications). |
|||
|
|||
### Configuration in `HealthChecksBuilderExtensions.cs` |
|||
|
|||
Health Checks are configured in the `HealthChecksBuilderExtensions` class. This class extends `IServiceCollection` to register health check services and configure health check UI endpoints. |
|||
|
|||
#### Default Configuration |
|||
|
|||
The default setup is as follows: |
|||
|
|||
```csharp |
|||
using HealthChecks.UI.Client; |
|||
using Microsoft.AspNetCore.Diagnostics.HealthChecks; |
|||
|
|||
namespace MyCompanyName.MyProjectName.HealthChecks; |
|||
|
|||
public static class HealthChecksBuilderExtensions |
|||
{ |
|||
public static void AddMyProjectNameHealthChecks(this IServiceCollection services) |
|||
{ |
|||
// Add your health checks here |
|||
var healthChecksBuilder = services.AddHealthChecks(); |
|||
healthChecksBuilder.AddCheck<MyProjectNameDatabaseCheck>("MyProjectName DbContext Check", tags: new string[] { "database" }); |
|||
|
|||
// Read configuration for health check URL |
|||
var configuration = services.GetConfiguration(); |
|||
var healthCheckUrl = configuration["App:HealthCheckUrl"] ?? "/health-status"; |
|||
|
|||
services.ConfigureHealthCheckEndpoint(healthCheckUrl); |
|||
|
|||
// Configure HealthChecks UI |
|||
var healthChecksUiBuilder = services.AddHealthChecksUI(settings => |
|||
{ |
|||
settings.AddHealthCheckEndpoint("MyProjectName Health Status", healthCheckUrl); |
|||
}); |
|||
|
|||
// Set HealthCheck UI storage |
|||
healthChecksUiBuilder.AddInMemoryStorage(); |
|||
|
|||
services.MapHealthChecksUiEndpoints(options => |
|||
{ |
|||
options.UIPath = "/health-ui"; |
|||
options.ApiPath = "/health-api"; |
|||
}); |
|||
} |
|||
|
|||
private static IServiceCollection ConfigureHealthCheckEndpoint(this IServiceCollection services, string path) |
|||
{ |
|||
.... |
|||
} |
|||
|
|||
private static IServiceCollection MapHealthChecksUiEndpoints(this IServiceCollection services, Action<global::HealthChecks.UI.Configuration.Options>? setupOption = null) |
|||
{ |
|||
.... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Database Health Check Implementation |
|||
|
|||
The `MyProjectNameDatabaseCheck` class is a custom implementation of a health check that verifies database connectivity in the applications with database connection. Example: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Diagnostics.HealthChecks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace MyCompanyName.MyProjectName.HealthChecks; |
|||
|
|||
public class MyProjectNameDatabaseCheck : IHealthCheck, ITransientDependency |
|||
{ |
|||
protected readonly IIdentityRoleRepository IdentityRoleRepository; |
|||
|
|||
public MyProjectNameDatabaseCheck(IIdentityRoleRepository identityRoleRepository) |
|||
{ |
|||
IdentityRoleRepository = identityRoleRepository; |
|||
} |
|||
|
|||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) |
|||
{ |
|||
try |
|||
{ |
|||
await IdentityRoleRepository.GetListAsync(sorting: nameof(IdentityRole.Id), maxResultCount: 1, cancellationToken: cancellationToken); |
|||
return HealthCheckResult.Healthy($"Could connect to database and get record."); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return HealthCheckResult.Unhealthy($"Error when trying to get database record. ", e); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
@ -0,0 +1,115 @@ |
|||
# Single Layer Solution: Health Check Configuration |
|||
|
|||
```json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "CORS Configuration", |
|||
"Path": "solution-templates/single-layer-web-application/cors-configuration" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Health Check is a feature that allows applications to monitor their health and diagnose potential issues. The single-layer solution template comes with pre-configured Health Check system. |
|||
|
|||
In the single-layer solution template, Health Check configuration is applied in the following cases: |
|||
|
|||
- When [MVC](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#mvc) is selected as the web application type. |
|||
- When [Blazor Server](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#blazor-server) is selected as the web application type. |
|||
- When [Angular](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#angular) is selected as the web application type (configured at the backend). |
|||
- When [No UI](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#no-ui) is selected as the web application type (configured at the backend). |
|||
|
|||
### Configuration in `HealthChecksBuilderExtensions.cs` |
|||
|
|||
Health Checks are configured in the `HealthChecksBuilderExtensions` class. This class extends `IServiceCollection` to register health check services and configure health check UI endpoints. |
|||
|
|||
#### Default Configuration |
|||
|
|||
The default setup is as follows: |
|||
|
|||
```csharp |
|||
using HealthChecks.UI.Client; |
|||
using Microsoft.AspNetCore.Diagnostics.HealthChecks; |
|||
|
|||
namespace MyCompanyName.MyProjectName.HealthChecks; |
|||
|
|||
public static class HealthChecksBuilderExtensions |
|||
{ |
|||
public static void AddMyProjectNameHealthChecks(this IServiceCollection services) |
|||
{ |
|||
// Add your health checks here |
|||
var healthChecksBuilder = services.AddHealthChecks(); |
|||
healthChecksBuilder.AddCheck<MyProjectNameDatabaseCheck>("MyProjectName DbContext Check", tags: new string[] { "database" }); |
|||
|
|||
// Read configuration for health check URL |
|||
var configuration = services.GetConfiguration(); |
|||
var healthCheckUrl = configuration["App:HealthCheckUrl"] ?? "/health-status"; |
|||
|
|||
services.ConfigureHealthCheckEndpoint(healthCheckUrl); |
|||
|
|||
// Configure HealthChecks UI |
|||
var healthChecksUiBuilder = services.AddHealthChecksUI(settings => |
|||
{ |
|||
settings.AddHealthCheckEndpoint("MyProjectName Health Status", healthCheckUrl); |
|||
}); |
|||
|
|||
// Set HealthCheck UI storage |
|||
healthChecksUiBuilder.AddInMemoryStorage(); |
|||
|
|||
services.MapHealthChecksUiEndpoints(options => |
|||
{ |
|||
options.UIPath = "/health-ui"; |
|||
options.ApiPath = "/health-api"; |
|||
}); |
|||
} |
|||
|
|||
private static IServiceCollection ConfigureHealthCheckEndpoint(this IServiceCollection services, string path) |
|||
{ |
|||
.... |
|||
} |
|||
|
|||
private static IServiceCollection MapHealthChecksUiEndpoints(this IServiceCollection services, Action<global::HealthChecks.UI.Configuration.Options>? setupOption = null) |
|||
{ |
|||
.... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Database Health Check Implementation |
|||
|
|||
The `MyProjectNameDatabaseCheck` class is a custom implementation of a health check that verifies database connectivity using `IIdentityRoleRepository`. |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Diagnostics.HealthChecks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace MyCompanyName.MyProjectName.HealthChecks; |
|||
|
|||
public class MyProjectNameDatabaseCheck : IHealthCheck, ITransientDependency |
|||
{ |
|||
protected readonly IIdentityRoleRepository IdentityRoleRepository; |
|||
|
|||
public MyProjectNameDatabaseCheck(IIdentityRoleRepository identityRoleRepository) |
|||
{ |
|||
IdentityRoleRepository = identityRoleRepository; |
|||
} |
|||
|
|||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) |
|||
{ |
|||
try |
|||
{ |
|||
await IdentityRoleRepository.GetListAsync(sorting: nameof(IdentityRole.Id), maxResultCount: 1, cancellationToken: cancellationToken); |
|||
return HealthCheckResult.Healthy($"Could connect to database and get record."); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return HealthCheckResult.Unhealthy($"Error when trying to get database record. ", e); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 30 KiB |