@ -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: 25 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 206 KiB |
@ -0,0 +1,43 @@ |
|||
**WE HAD A BLAST AT BASTA\! FRANKFURT 2025** |
|||
|
|||
 |
|||
|
|||
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: 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,8 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Domain.Entities.Events; |
|||
|
|||
internal class EntitySelectorList : List<NamedTypeSelector>, IEntitySelectorList |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Domain.Entities.Events; |
|||
|
|||
public static class EntitySelectorListExtensions |
|||
{ |
|||
public static IEntitySelectorList Add(this IEntitySelectorList selectors, string name, Func<Type, bool> predicate) |
|||
{ |
|||
selectors.Add(new NamedTypeSelector(name, predicate)); |
|||
return selectors; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Domain.Entities.Events; |
|||
|
|||
public interface IEntitySelectorList : IList<NamedTypeSelector> |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Text.RegularExpressions; |
|||
using Hangfire; |
|||
using Hangfire.States; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace Volo.Abp.Hangfire; |
|||
|
|||
public class AbpHangfireOptionsConfiguration : IPostConfigureOptions<AbpHangfireOptions> |
|||
{ |
|||
public void PostConfigure(string? name, AbpHangfireOptions options) |
|||
{ |
|||
if (options.DefaultQueuePrefix.IsNullOrWhiteSpace()) |
|||
{ |
|||
return;; |
|||
} |
|||
|
|||
// The Queue name argument must consist of lowercase letters, digits, underscore, and dash characters only.
|
|||
var queuesPrefix = Regex.Replace(options.DefaultQueuePrefix.ToLower().Replace(".", "_"), "[^a-z0-9_-]", ""); |
|||
if (queuesPrefix.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new AbpException($"The QueuesPrefix({options.DefaultQueuePrefix}) is not valid, it must consist of lowercase letters, digits, underscore, and dash characters only."); |
|||
} |
|||
|
|||
options.DefaultQueuePrefix = queuesPrefix.EnsureEndsWith('_'); |
|||
|
|||
if (options.ServerOptions == null) |
|||
{ |
|||
var queue = $"{options.DefaultQueuePrefix}{EnqueuedState.DefaultQueue}"; |
|||
if (queue.Length > options.MaxQueueNameLength) |
|||
{ |
|||
throw new AbpException($"The maximum length of the Hangfire queue name({queue}) is {options.MaxQueueNameLength}, Please configure the AbpHangfireOptions.DefaultQueuePrefix manually."); |
|||
} |
|||
options.ServerOptions = new BackgroundJobServerOptions |
|||
{ |
|||
Queues = new[] { queue } |
|||
}; |
|||
options.DefaultQueue = queue; |
|||
} |
|||
else |
|||
{ |
|||
var queues = options.ServerOptions.Queues; |
|||
for (var i = 0; i < queues.Length; i++) |
|||
{ |
|||
var queue = $"{options.DefaultQueuePrefix}{queues[i]}"; |
|||
if (queue.Length > options.MaxQueueNameLength) |
|||
{ |
|||
throw new AbpException($"The maximum length of the Hangfire queue name({queue}) is {options.MaxQueueNameLength}, Please configure the AbpHangfireOptions.DefaultQueuePrefix manually."); |
|||
} |
|||
queues[i] = queue; |
|||
} |
|||
var defaultQueue = queues.FirstOrDefault(q => q.EndsWith(EnqueuedState.DefaultQueue)); |
|||
if (defaultQueue.IsNullOrWhiteSpace()) |
|||
{ |
|||
defaultQueue = queues.FirstOrDefault(); |
|||
if (defaultQueue.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new AbpException("There is no queue defined in the Hangfire configuration!"); |
|||
} |
|||
} |
|||
options.DefaultQueue = defaultQueue; |
|||
} |
|||
} |
|||
} |
|||