@ -0,0 +1,302 @@ |
|||
# Where and How to Store Your BLOB Objects in .NET? |
|||
|
|||
When building modern web applications, managing [BLOBs (Binary Large Objects)](https://cloud.google.com/discover/what-is-binary-large-object-storage) such as images, videos, documents, or any other file types is a common requirement. Whether you're developing a CMS, an e-commerce platform, or almost any other kind of application, you'll eventually ask yourself: **"Where should I store these files?"** |
|||
|
|||
In this article, we'll explore different approaches to storing BLOBs in .NET applications and demonstrate how the ABP Framework simplifies this process with its flexible [BLOB Storing infrastructure](https://abp.io/docs/latest/framework/infrastructure/blob-storing). |
|||
|
|||
ABP Provides [multiple storage providers](https://abp.io/docs/latest/framework/infrastructure/blob-storing#blob-storage-providers) such as Azure, AWS, Google, Minio, Bunny etc. But for the simplicity of this article, we will only focus on the **Database Provider**, showing you how to store BLOBs in database tables step-by-step. |
|||
|
|||
## Understanding BLOB Storage Options |
|||
|
|||
Before diving into implementation details, let's understand the common approaches for storing BLOBs in .NET applications. Mainly, there are three main approaches: |
|||
|
|||
1. Database Storage |
|||
2. File System Storage |
|||
3. Cloud Storage |
|||
|
|||
### 1. Database Storage |
|||
|
|||
The first approach is to store BLOBs directly in the database alongside your relational data (_you can also store them separately_). This approach uses columns with types like `VARBINARY(MAX)` in SQL Server or `BYTEA` in PostgreSQL. |
|||
|
|||
**Pros:** |
|||
- ✅ Transactional consistency between files and related data |
|||
- ✅ Simplified backup and restore operations (everything in one place) |
|||
- ✅ No additional file system permissions or management needed |
|||
|
|||
**Cons:** |
|||
- ❌ Database size can grow significantly with large files |
|||
- ❌ Potential performance impact on database operations |
|||
- ❌ May require additional database tuning and optimization |
|||
- ❌ Increased backup size and duration |
|||
|
|||
### 2. File System Storage |
|||
|
|||
The second obvious approach is to store BLOBs as physical files in the server's file system. This approach is simple and easy to implement. Also, it's possible to use these two approaches together and keep the metadata and file references in the database. |
|||
|
|||
**Pros:** |
|||
- ✅ Better performance for large files |
|||
- ✅ Reduced database size and improved database performance |
|||
- ✅ Easier to leverage CDNs and file servers |
|||
- ✅ Simple to implement file system-level operations (compression, deduplication) |
|||
|
|||
**Cons:** |
|||
- ❌ Requires separate backup strategy for files |
|||
- ❌ Need to manage file system permissions |
|||
- ❌ Potential synchronization issues in distributed environments |
|||
- ❌ More complex cleanup operations for orphaned files |
|||
|
|||
### 3. Cloud Storage (Azure, AWS S3, etc.) |
|||
|
|||
The third approach can be using cloud storage services for scalability and global distribution. This approach is powerful and scalable. But it's also more complex to implement and manage. |
|||
|
|||
**Best for:** |
|||
- Large-scale applications |
|||
- Multi-region deployments |
|||
- Content delivery requirements |
|||
|
|||
## ABP Framework's BLOB Storage Infrastructure |
|||
|
|||
The ABP Framework provides an abstraction layer over different storage providers, allowing you to switch between them with minimal code changes. This is achieved through the **IBlobContainer** (and `IBlobContainer<TContainerType>`) service and various provider implementations. |
|||
|
|||
> ABP provides several built-in providers, which you can see the full list [here](https://abp.io/docs/latest/framework/infrastructure/blob-storing#blob-storage-providers). |
|||
|
|||
Let's see how to use the Database provider in your application step by step. |
|||
|
|||
### Demo: Storing BLOBs in Database in an ABP-Based Application |
|||
|
|||
In this demo, we'll walk through a practical example of storing BLOBs in a database using ABP's BLOB Storing infrastructure. We'll focus on the backend implementation using the `IBlobContainer` service and examine the database structure that ABP creates automatically. The UI framework choice doesn't matter for this demonstration, as we're concentrating on the core BLOB storage functionality. |
|||
|
|||
If you don't have an ABP application yet, create one using the ABP CLI: |
|||
|
|||
```bash |
|||
abp new BlobStoringDemo |
|||
``` |
|||
|
|||
This command generates a new ABP layered application named `BlobStoringDemo` with **MVC** as the default UI and **SQL Server** as the default database provider. |
|||
|
|||
#### Understanding the Database Provider Setup |
|||
|
|||
When you create a layered ABP application, it automatically includes the BLOB Storing infrastructure with the Database Provider pre-configured. You can verify this by examining the module dependencies in your `*Domain`, `*DomainShared`, and `*EntityFrameworkCore` modules: |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
//... |
|||
typeof(BlobStoringDatabaseDomainModule) // <-- This is the Database Provider |
|||
)] |
|||
public class BlobStoringDemoDomainModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
Since the Database Provider is already included through module dependencies, no additional configuration is required to start using it. The provider is ready to use out of the box. |
|||
|
|||
However, if you're working with multiple BLOB storage providers or want to explicitly configure the Database Provider, you can add the following configuration to your `*EntityFrameworkCore` module's `ConfigureServices` method: |
|||
|
|||
```csharp |
|||
Configure<AbpBlobStoringOptions>(options => |
|||
{ |
|||
options.Containers.ConfigureDefault(container => |
|||
{ |
|||
container.UseDatabase(); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
> **Note:** This explicit configuration is optional when using only one BLOB provider (Database Provider in this case), but becomes necessary when managing multiple providers or custom container configurations. |
|||
|
|||
#### Running Database Migrations |
|||
|
|||
Now, let's apply the database migrations to create the necessary BLOB storage tables. Run the `DbMigrator` project: |
|||
|
|||
```bash |
|||
cd src/BlobStoringDemo.DbMigrator |
|||
dotnet run |
|||
``` |
|||
|
|||
Once the migration completes successfully, open your database management tool and you'll see two new tables: |
|||
|
|||
 |
|||
|
|||
**Understanding the BLOB Storage Tables:** |
|||
|
|||
- **`AbpBlobContainers`**: Stores metadata about BLOB containers, including container names, tenant information, and any custom properties. |
|||
|
|||
- **`AbpBlobs`**: Stores the actual BLOB content (the binary data) along with references to their parent containers. Each BLOB is associated with a container through a foreign key relationship. |
|||
|
|||
When you save a BLOB, ABP automatically handles the database operations: the binary content goes into `AbpBlobs`, while the container configuration and metadata are managed in `AbpBlobContainers`. |
|||
|
|||
#### Creating a File Management Service |
|||
|
|||
Let's implement a practical application service that demonstrates common BLOB operations. Create a new application service class: |
|||
|
|||
```csharp |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.BlobStoring; |
|||
|
|||
namespace BlobStoringDemo |
|||
{ |
|||
public class FileAppService : ApplicationService, IFileAppService |
|||
{ |
|||
private readonly IBlobContainer _blobContainer; |
|||
|
|||
public FileAppService(IBlobContainer blobContainer) |
|||
{ |
|||
_blobContainer = blobContainer; |
|||
} |
|||
|
|||
public async Task SaveFileAsync(string fileName, byte[] fileContent) |
|||
{ |
|||
// Save the file |
|||
await _blobContainer.SaveAsync(fileName, fileContent); |
|||
} |
|||
|
|||
public async Task<byte[]> GetFileAsync(string fileName) |
|||
{ |
|||
// Get the file |
|||
return await _blobContainer.GetAllBytesAsync(fileName); |
|||
} |
|||
|
|||
public async Task<bool> FileExistsAsync(string fileName) |
|||
{ |
|||
// Check if file exists |
|||
return await _blobContainer.ExistsAsync(fileName); |
|||
} |
|||
|
|||
public async Task DeleteFileAsync(string fileName) |
|||
{ |
|||
// Delete the file |
|||
await _blobContainer.DeleteAsync(fileName); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Here, we are doing the followings: |
|||
|
|||
- Injecting the `IBlobContainer` service. |
|||
- Saving the BLOB data to the database with the `SaveAsync` method. (_it allows you to use byte arrays or streams_) |
|||
- Retrieving the BLOB data from the database with the `GetAllBytesAsync` method. |
|||
- Checking if the BLOB exists with the `ExistsAsync` method. |
|||
- Deleting the BLOB data from the database with the `DeleteAsync` method. |
|||
|
|||
With this service in place, you can now manage BLOBs throughout your application without worrying about the underlying storage implementation. Simply inject `IFileAppService` wherever you need file operations, and ABP handles all the provider-specific details behind the scenes. |
|||
|
|||
> Also, it's good to highlight that, the beauty of this approach is **provider independence**: you can start with database storage and later switch to Azure Blob Storage, AWS S3, or any other provider without modifying a single line of your application code. We'll explore this powerful feature in the next section. |
|||
|
|||
### Switching Between Providers |
|||
|
|||
One of the biggest advantages of using ABP's BLOB Storage system is the ability to switch providers without changing your application code. |
|||
|
|||
For example, you might start with the [File System provider](https://abp.io/docs/latest/framework/infrastructure/blob-storing/file-system) during development and switch to [Azure Blob Storage](https://abp.io/docs/latest/framework/infrastructure/blob-storing/azure) for production: |
|||
|
|||
**Development:** |
|||
```csharp |
|||
Configure<AbpBlobStoringOptions>(options => |
|||
{ |
|||
options.Containers.ConfigureDefault(container => |
|||
{ |
|||
container.UseFileSystem(fileSystem => |
|||
{ |
|||
fileSystem.BasePath = Path.Combine( |
|||
hostingEnvironment.ContentRootPath, |
|||
"Documents" |
|||
); |
|||
}); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
**Production:** |
|||
```csharp |
|||
Configure<AbpBlobStoringOptions>(options => |
|||
{ |
|||
options.Containers.ConfigureDefault(container => |
|||
{ |
|||
container.UseAzure(azure => |
|||
{ |
|||
azure.ConnectionString = "your azure connection string"; |
|||
azure.ContainerName = "your azure container name"; |
|||
azure.CreateContainerIfNotExists = true; |
|||
}); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
**Your application code remains unchanged!** You just need to install the appropriate package and update the configuration. You can even use pragmas (for example: `#if !DEBUG`) to switch the provider at runtime (or use similar techniques). |
|||
|
|||
### Using Named BLOB Containers |
|||
|
|||
ABP allows you to define multiple BLOB containers with different configurations. This is useful when you need to store different types of files using different providers. Here are the steps to implement it: |
|||
|
|||
#### Step 1: Define a BLOB Container |
|||
|
|||
```csharp |
|||
[BlobContainerName("profile-pictures")] |
|||
public class ProfilePictureContainer |
|||
{ |
|||
} |
|||
|
|||
[BlobContainerName("documents")] |
|||
public class DocumentContainer |
|||
{ |
|||
} |
|||
``` |
|||
|
|||
#### Step 2: Configure Different Providers for Each Container |
|||
|
|||
```csharp |
|||
Configure<AbpBlobStoringOptions>(options => |
|||
{ |
|||
// Profile pictures stored in database |
|||
options.Containers.Configure<ProfilePictureContainer>(container => |
|||
{ |
|||
container.UseDatabase(); |
|||
}); |
|||
|
|||
// Documents stored in file system |
|||
options.Containers.Configure<DocumentContainer>(container => |
|||
{ |
|||
container.UseFileSystem(fileSystem => |
|||
{ |
|||
fileSystem.BasePath = Path.Combine( |
|||
hostingEnvironment.ContentRootPath, |
|||
"Documents" |
|||
); |
|||
}); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
#### Step 3: Use the Named Containers |
|||
|
|||
Once you have defined the BLOB Containers, you can use the `IBlobContainer<TContainerType>` service to access the BLOB containers: |
|||
|
|||
```csharp |
|||
public class ProfileService : ApplicationService |
|||
{ |
|||
private readonly IBlobContainer<ProfilePictureContainer> _profilePictureContainer; |
|||
|
|||
public ProfileService(IBlobContainer<ProfilePictureContainer> profilePictureContainer) |
|||
{ |
|||
_profilePictureContainer = profilePictureContainer; |
|||
} |
|||
|
|||
public async Task UpdateProfilePictureAsync(Guid userId, byte[] picture) |
|||
{ |
|||
var blobName = $"{userId}.jpg"; |
|||
await _profilePictureContainer.SaveAsync(blobName, picture); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
With this approach, your documents and profile pictures are stored in different containers and different providers. This is useful when you need to store different types of files using different providers and need scalability and performance. |
|||
|
|||
## Conclusion |
|||
|
|||
Managing BLOBs effectively is crucial for modern applications, and choosing the right storage approach depends on your specific needs. |
|||
|
|||
ABP's BLOB Storing infrastructure provides a powerful abstraction that lets you start with one provider and switch to another as your requirements evolve, all without changing your application code. |
|||
|
|||
Whether you're storing files in a database, file system, or cloud storage, ABP's BLOB Storing system provides a flexible and powerful way to manage your files. |
|||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 165 KiB |
@ -0,0 +1,134 @@ |
|||
# Elsa Module (Pro) |
|||
|
|||
> You must have an ABP Team or a higher license to use this module. |
|||
|
|||
This module integrates [Elsa Workflows](https://docs.elsaworkflows.io/) into ABP Framework applications and is designed to make it easy for developers to use Elsa's capabilities within their ABP-based projects. For creating, managing, and customizing workflows themselves, please refer to [the official Elsa documentation](https://docs.elsaworkflows.io/). |
|||
|
|||
## How to install |
|||
|
|||
The Elsa module is not installed in [the startup templates](../solution-templates/layered-web-application) by default and must be installed manually. There are two ways of installing a module into your application and each one of these approaches is explained in the next sections. |
|||
|
|||
### Using ABP CLI |
|||
|
|||
ABP CLI allows adding a module to a solution using the ```add-module``` command. You can check its [documentation](../cli#add-module) for more information. So, the Elsa module can be added using the following command: |
|||
|
|||
```bash |
|||
abp add-module Volo.Elsa |
|||
``` |
|||
|
|||
### Manual Installation |
|||
|
|||
If you modified your solution structure, adding the module using ABP CLI might not work for you. In such cases, you can add the Elsa module into your solution manually. |
|||
|
|||
In order to do that, add packages listed below to the matching project in your solution. For example, `Volo.Abp.Elsa.Application` package to your **{ProjectName}.Application.csproj** as shown below: |
|||
|
|||
```xml |
|||
<PackageReference Include="Volo.Abp.Elsa.Application" Version="x.x.x" /> |
|||
``` |
|||
|
|||
After adding the package references, open the module class of the project (e.g.: `{ProjectName}ApplicationModule`) and add the code below to the `DependsOn` attribute: |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
//... |
|||
typeof(AbpElsaApplicationModule) |
|||
)] |
|||
``` |
|||
|
|||
> If you are using Blazor Web App, you need to add the `Volo.Elsa.Admin.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and add the `Volo.Elsa.Admin.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project. |
|||
|
|||
## The Elsa Module |
|||
|
|||
The Elsa Workflows has its own database provider, and also has a Tenant/Role/User system. They are under active development, so the ABP Elsa module is not yet fully integrated. Below is the current status of each module in the ABP's Elsa Module: |
|||
|
|||
- `AbpElsaAspNetCoreModule(Volo.Elsa.Abp.AspNetCore)` module is used to integrate Elsa authentication. |
|||
- `AbpElsaIdentityModule(Volo.Elsa.Abp.Identity)` module is used to integrate ABP Identity authentication. |
|||
- `AbpElsaApplicationModule(Volo.Elsa.Abp.Application)` and `AbpElsaApplicationContractsModule(Volo.Elsa.Abp.Application.Contracts)` modules are used to define the Elsa permissions. |
|||
|
|||
The rest of the projects/modules are basically empty and will be implemented in the future based on the Elsa features: |
|||
|
|||
- `AbpElsaDomainModule(Volo.Elsa.Abp.Domain)` |
|||
- `AbpElsaEntityFrameworkCoreModule(Volo.Elsa.Abp.EntityFrameworkCore)` |
|||
- `AbpElsaHttpApiModule(Volo.Elsa.Abp.HttpApi)` |
|||
- `AbpElsaHttpApiClientModule(Volo.Elsa.Abp.HttpApi.Client)` |
|||
- `AbpElsaBlazorModule(Volo.Elsa.Abp.Blazor)` |
|||
- `AbpElsaBlazorServerModule(Volo.Elsa.Abp.Blazor.Server)` |
|||
- `AbpElsaBlazorWebAssemblyModule(Volo.Elsa.Abp.Blazor.WebAssembly)` |
|||
- `AbpElsaWebModule(Volo.Elsa.Abp.Web)` |
|||
|
|||
### Elsa Module Permissions |
|||
|
|||
The Elsa Workflow API endpoints check permissions. Also, it has a `*` wildcard permission to allow all permissions. |
|||
|
|||
The ABP Elsa module defines all permissions that are used in the Elsa workflow. You can use ABP Permission Management module to manage the permissions. |
|||
|
|||
`AbpElsaAspNetCoreModule(Volo.Elsa.Abp.AspNetCore)` module will check and add these permissions to the current user's claims: |
|||
|
|||
 |
|||
|
|||
You can also grant parts of the permissions to a role or user. It will add the `permissions` claims to the current user's `Cookies` or `Token`. Elsa Server will read the claims and allow or deny access: |
|||
|
|||
 |
|||
|
|||
### Elsa Studio |
|||
|
|||
Elsa Studio is an **independent** web application that allows you to design, manage, and execute workflows. It is built using **Blazor Server/WebAssembly**. |
|||
|
|||
Elsa Studio requires authentication and there are two ways to authenticate Elsa Studio: |
|||
|
|||
* Password Flow Authentication |
|||
* Code Flow Authentication |
|||
|
|||
#### Elsa Studio - Password Flow Authentication |
|||
|
|||
The `AbpElsaIdentityModule(Volo.Elsa.Abp.Identity)` module is used to integrate with [ABP Identity module](./identity-pro.md) to check Elsa Studio *username* and *password* against ABP Identity. |
|||
|
|||
You need to replace `UseIdentity` with `UseAbpIdentity` when configuring Elsa in your Elsa server project as follows: |
|||
|
|||
```csharp |
|||
context.Services |
|||
.AddElsa(elsa => elsa |
|||
.UseAbpIdentity(identity => |
|||
{ |
|||
identity.TokenOptions = options => options.SigningKey = "large-signing-key-for-signing-JWT-tokens"; |
|||
}); |
|||
); |
|||
``` |
|||
|
|||
After that, you can add the below code to use `Identity` as the login method in your Elsa Studio client project: |
|||
|
|||
```csharp |
|||
builder.Services.AddLoginModule().UseElsaIdentity(); |
|||
``` |
|||
|
|||
Then, you can log in to the Elsa Studio application with the default credentials (`admin` as the username, and `1q2w3E*` as the password): |
|||
|
|||
 |
|||
|
|||
Once, you logged in to the application, you can start defining workflows, manage them and see their execution instances and more: |
|||
|
|||
 |
|||
|
|||
#### Elsa Studio - Code Flow Authentication |
|||
|
|||
ABP applications use [OpenIddict](./openiddict-pro.md) for authentication. So, you can use the [Authorization Code Flow](https://oauth.net/2/grant-types/authorization-code/) to authenticate Elsa Studio. |
|||
|
|||
To do that, you can add the code block below to your Elsa Studio client project: |
|||
|
|||
```csharp |
|||
builder.Services.AddLoginModule().UseOpenIdConnect(connectConfiguration => |
|||
{ |
|||
var authority = configuration["AuthServer:Authority"]!.TrimEnd('/'); // Your Server URL |
|||
connectConfiguration.AuthEndpoint = $"{authority}/connect/authorize"; |
|||
connectConfiguration.TokenEndpoint = $"{authority}/connect/token"; |
|||
connectConfiguration.EndSessionEndpoint = $"{authority}/connect/endsession"; |
|||
connectConfiguration.ClientId = configuration["AuthServer:ClientId"]!; |
|||
connectConfiguration.Scopes = ["openid", "profile", "email", "phone", "roles", "offline_access", "ElsaDemoAppServer"]; |
|||
}); |
|||
``` |
|||
|
|||
After that, Elsa Studio will redirect to your ABP application's login page, then redirect back to Elsa Studio after the successful login. |
|||
|
|||
### Elsa Workflows - Sample Workflow Demo |
|||
|
|||
ABP provides a complete demo application that shows how to use the Elsa module in your ABP application. You can download the demo application and see the integration points, if you stuck at any point. Please see the [Elsa Workflows - Sample Workflow Demo](../samples/elsa-workflows.md) page for more information. |
|||
@ -0,0 +1,73 @@ |
|||
# Elsa Workflows - Sample Workflow Demo |
|||
|
|||
The `ElsaDemoApp` is a sample application that demonstrates how to use the [Elsa](https://github.com/elsa-workflows/elsa-core) module in an ABP application. The demo application consists of four projects: |
|||
|
|||
- `ElsaDemoApp.Server` is an ABP application with Identity and Elsa modules. It is used as the authentication server and Elsa workflow server. |
|||
- `ElsaDemoApp.Studio.WASM` is a Blazor WebAssembly application with Elsa Studio. It is used as the Elsa Studio client application. |
|||
- `ElsaDemoApp.Ordering` and `ElsaDemoApp.Payment` are two microservices that can be used to test the Elsa workflows in distributed systems. |
|||
|
|||
 |
|||
|
|||
> **This sample workflow demonstrates how to integrate ABP with Elsa Workflows.** For more detailed information about Elsa itself, please refer to the official [Elsa documentation](https://docs.elsaworkflows.io/) and related guides. |
|||
|
|||
## Download |
|||
|
|||
> **Note:** The `ElsaDemoApp` sample application is only for the **ABP customers**. Therefore, you need to have a commercial license to be able to download the source code. |
|||
|
|||
* You can download the complete source-code from [https://abp.io/api/download/samples/elsaworkflow](https://abp.io/Account/Login?returnUrl=/api/download/samples/elsaworkflow) |
|||
|
|||
## Running the Demo Application |
|||
|
|||
The `ElsaDemoApp.Server` has a pre-defined Elsa workflow that creates an order and processes the payment using Elsa workflows, and uses ABP distributed event bus to coordinate the workflow. |
|||
|
|||
Here is the complete workflow in code: |
|||
|
|||
```cs |
|||
public class OrderWorkflow : WorkflowBase |
|||
{ |
|||
public const string Name = "OrderWorkflow"; |
|||
|
|||
protected override void Build(IWorkflowBuilder builder) |
|||
{ |
|||
builder.WithDefinitionId(Name); |
|||
builder.Root = new Sequence |
|||
{ |
|||
Activities = |
|||
{ |
|||
// Will publish NewOrderEto event to the Ordering microservice, Ordering microservice will create the order and publish OrderPlaced event |
|||
new CreateOrderActivity(), |
|||
|
|||
// Wait for the OrderPlaced event, This event is triggered by the Ordering microservice, and Elsa will make workflow continue to the next activity |
|||
new OrderPlacedEvent(), |
|||
|
|||
// This activity will publish RequestPaymentEto event to the Payment microservice, Payment microservice will process the payment and publish PaymentCompleted event |
|||
new RequestPaymentActivity(), |
|||
|
|||
// Wait for the PaymentCompleted event, This event is triggered by the Payment microservice, and Elsa will make workflow continue to the next activity |
|||
new PaymentCompletedEvent(), |
|||
|
|||
// This activity will send an email to the customer indicating that the payment is completed |
|||
new PaymentCompletedActivity() |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> The demo application uses SQL Server LocalDB as the database provider, Redis as the caching server and RabbitMQ for the message broker. Please make sure you have them installed and running on your machine and then follow the instructions below to run the application. |
|||
|
|||
You can apply the following steps to run the demo application: |
|||
|
|||
1. Run `ElsaDemoApp.Server` project to migrate the database(`dotnet run --migrate-database`) and start the server. |
|||
2. Run `ElsaDemoApp.Studio.WASM` project to start the Elsa Studio client application. |
|||
3. Run `ElsaDemoApp.Ordering` project to start the Ordering microservice. |
|||
4. Run `ElsaDemoApp.Payment` project to start the Payment microservice. |
|||
|
|||
After running all the applications, you can log in to the `ElsaDemoApp.Server` application (with the default credentials) and navigate to the `https://localhost:5001/Ordering` page to create an order: |
|||
|
|||
 |
|||
|
|||
After that, you can navigate to the `ElsaDemoApp.Studio.WASM` application and see the workflow instance created, running, and completed: |
|||
|
|||
 |
|||
|
|||
@ -0,0 +1,39 @@ |
|||
{ |
|||
"packages":[ |
|||
{ |
|||
"name": "@abp/ng.account", |
|||
"appRoutingModuleConfiguration":{ |
|||
"routes":[ |
|||
"{ path: 'account', loadChildren: () => import('@abp/ng.account').then(c => c.createRoutes()),}" |
|||
] |
|||
}, |
|||
"appModuleConfiguration":{ |
|||
"imports":[ |
|||
{ |
|||
"names":[ |
|||
"provideAccountConfig" |
|||
], |
|||
"namespace": "@abp/ng.account/config" |
|||
} |
|||
], |
|||
"providerNames":[ |
|||
"provideAccountConfig()" |
|||
] |
|||
}, |
|||
"tsJsonPathRecordConfigurations":[ |
|||
{ |
|||
"name": "@abp/ng.account", |
|||
"paths": [ |
|||
"angular/projects/account/src/public-api.ts" |
|||
] |
|||
}, |
|||
{ |
|||
"name": "@abp/ng.account/config", |
|||
"paths": [ |
|||
"angular/projects/account/config/src/public-api.ts" |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
{ |
|||
"packages":[ |
|||
{ |
|||
"name": "@abp/ng.identity", |
|||
"appRoutingModuleConfiguration":{ |
|||
"routes":[ |
|||
"{ path: 'identity', loadChildren: () => import('@abp/ng.identity').then(c => c.createRoutes()),}" |
|||
] |
|||
}, |
|||
"appModuleConfiguration":{ |
|||
"imports":[ |
|||
{ |
|||
"names":[ |
|||
"provideIdentityConfig" |
|||
], |
|||
"namespace": "@abp/ng.identity/config" |
|||
} |
|||
], |
|||
"providerNames":[ |
|||
"provideIdentityConfig()" |
|||
] |
|||
}, |
|||
"tsJsonPathRecordConfigurations":[ |
|||
{ |
|||
"name": "@abp/ng.identity", |
|||
"paths": [ |
|||
"angular/projects/@abp/ng.identity/src/public-api.ts" |
|||
] |
|||
}, |
|||
{ |
|||
"name": "@abp/ng.identity/config", |
|||
"paths": [ |
|||
"angular/projects/identity/config/src/public-api.ts" |
|||
] |
|||
}, |
|||
{ |
|||
"name": "@abp/ng.identity/proxy", |
|||
"paths": [ |
|||
"angular/projects/identity/proxy/src/public-api.ts" |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
{ |
|||
"packages":[ |
|||
{ |
|||
"name": "@abp/ng.permission-management", |
|||
"tsJsonPathRecordConfigurations":[ |
|||
{ |
|||
"name": "@abp/ng.permission-management", |
|||
"paths": [ |
|||
"angular/projects/permission-management/src/public-api.ts" |
|||
] |
|||
}, |
|||
{ |
|||
"name": "@abp/ng.permission-management/proxy", |
|||
"paths": [ |
|||
"angular/projects/permission-management/proxy/src/public-api.ts" |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||