Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
ff29dd0
Initial plan
Copilot Sep 28, 2025
bdcc697
Add TickerQ background worker integration
Copilot Sep 28, 2025
071ef3b
Complete TickerQ integration with tests, samples, and documentation
Copilot Sep 28, 2025
e21a776
Add net10.0 target framework to TickerQ projects
Copilot Sep 29, 2025
e3260f5
article added
erdemcaygor Sep 29, 2025
83f9542
Add article on distributed locking in ASP.NET Core
fahrigedik Sep 30, 2025
6565c56
Optimised images with calibre/image-actions
github-actions[bot] Sep 30, 2025
d26d299
Add version 9.3.5 to latest-versions.json
skoc10 Sep 30, 2025
87a48e5
Merge pull request #23826 from abpframework/auto-merge/rel-10-0/3995
maliming Sep 30, 2025
40866fb
initial commit for the blob storing article
EngincanV Sep 30, 2025
628ec35
Update POST.md
EngincanV Sep 30, 2025
24cdfe6
Update POST.md
EngincanV Sep 30, 2025
ad903c7
Optimised images with calibre/image-actions
github-actions[bot] Sep 30, 2025
2ecffd9
Apply suggestions from code review
berkansasmaz Sep 30, 2025
61c02ea
Update docs/en/Community-Articles/2025-09-30-Where-and-How-to-Store-Y…
berkansasmaz Sep 30, 2025
564b67f
Optimised images with calibre/image-actions
github-actions[bot] Sep 30, 2025
c98d151
Merge pull request #23827 from abpframework/EngincanV/blob-article
berkansasmaz Sep 30, 2025
2af42bc
Merge pull request #23829 from abpframework/auto-merge/rel-10-0/3996
maliming Oct 1, 2025
4818885
Merge pull request #23831 from abpframework/auto-merge/rel-10-0/3997
maliming Oct 1, 2025
eee8cd1
Merge pull request #23832 from abpframework/auto-merge/rel-10-0/3998
maliming Oct 1, 2025
f99cd7e
fix: ract native overview document
sumeyyeKurtulus Oct 1, 2025
e07292d
Merge pull request #23834 from abpframework/fix/reactNativeDocParsing…
erdemcaygor Oct 1, 2025
84dfc2a
Update version and LeptonXVersion for nightly packages
skoc10 Oct 1, 2025
ada82b1
Merge pull request #23835 from abpframework/skoc10-patch-5
ebicoglu Oct 1, 2025
8317b6c
Merge pull request #23837 from abpframework/auto-merge/rel-10-0/4000
maliming Oct 1, 2025
24f11e7
Merge pull request #23840 from abpframework/auto-merge/rel-10-0/4002
maliming Oct 1, 2025
e01916d
Merge branch 'dev' into copilot/fix-d5496b38-0efc-4016-8ab2-970b67675314
maliming Oct 1, 2025
940e13b
Merge pull request #23841 from abpframework/auto-merge/rel-10-0/4003
maliming Oct 1, 2025
0433d5c
Merge pull request #23842 from abpframework/auto-merge/rel-10-0/4004
maliming Oct 1, 2025
11967e6
Update CLI sample to use --old flag
MansurBesleney Oct 1, 2025
e4517fb
Update sample command with -csf option
MansurBesleney Oct 1, 2025
9d69b80
Merge pull request #23843 from abpframework/vs-internal-#7077
oykuermann Oct 1, 2025
34eb2ed
Merge pull request #23811 from abpframework/issue-7132
ebicoglu Oct 2, 2025
357928b
Merge pull request #23853 from abpframework/auto-merge/rel-10-0/4006
maliming Oct 2, 2025
4357ce7
refactoring
erdemcaygor Oct 2, 2025
501e28a
Merge pull request #23855 from abpframework/auto-merge/rel-10-0/4008
maliming Oct 2, 2025
df9f7ab
article update
erdemcaygor Oct 2, 2025
9695182
Merge pull request #23859 from abpframework/auto-merge/rel-10-0/4010
maliming Oct 3, 2025
075d5bd
Merge pull request #23862 from abpframework/auto-merge/rel-10-0/4011
maliming Oct 3, 2025
b3dd0ac
Merge pull request #23864 from abpframework/auto-merge/rel-10-0/4012
maliming Oct 3, 2025
bfa10da
doc fix
fahrigedik Oct 3, 2025
05df37d
Merge pull request #23865 from abpframework/angular-fix-doc
erdemcaygor Oct 3, 2025
5d9a75b
Merge pull request #23866 from abpframework/auto-merge/rel-10-0/4013
maliming Oct 3, 2025
31ac0e0
Merge pull request #23825 from abpframework/skoc10-patch-3
gizemmutukurt Oct 3, 2025
1d8ab53
Close test email modal after sending email
maliming Oct 3, 2025
019ac06
Merge pull request #23870 from abpframework/auto-merge/rel-10-0/4014
maliming Oct 3, 2025
d1fb06e
Add new localization entries for token usage
AlperenSamurlu Oct 3, 2025
abdd45c
Add article on generating sequential GUIDs in .NET
enisn Oct 3, 2025
a879c1d
Optimised images with calibre/image-actions
github-actions[bot] Oct 3, 2025
253c036
Update docs/en/Community-Articles/2025-10-03-Generating-Sequential-GU…
enisn Oct 3, 2025
9dee271
Update docs/en/Community-Articles/2025-10-03-Generating-Sequential-GU…
enisn Oct 3, 2025
3326cca
Update Post.md
enisn Oct 3, 2025
4c0cb18
Merge branch 'article-sequential-guids' of https://github.com/abpfram…
enisn Oct 3, 2025
5ad1677
Update Post.md
enisn Oct 3, 2025
2c3863b
Merge pull request #23873 from abpframework/article-sequential-guids
EngincanV Oct 3, 2025
d87ad32
article update
erdemcaygor Oct 3, 2025
f6f8159
new article: AOT
ebicoglu Oct 3, 2025
4177061
feat: Introduce TickerQ background job integration
maliming Oct 4, 2025
94ac0d4
Merge branch 'dev' into copilot/fix-d5496b38-0efc-4016-8ab2-970b67675314
maliming Oct 4, 2025
3a9f035
feat: Implement TickerQ background worker management.
maliming Oct 5, 2025
ff2c239
Remove TickerQ background worker test project
maliming Oct 5, 2025
4a9f302
feat: Add TickerQ background job support and related modules
maliming Oct 5, 2025
b0a1a6b
feat: Add TickerQ integration to documentation and demo module
maliming Oct 5, 2025
5e23008
Add configurable retry options for TickerQ background jobs
maliming Oct 5, 2025
eff20f2
Add MyBackgroundWorker demo.
maliming Oct 5, 2025
fab7139
Add free disk space step to CI workflow
maliming Oct 5, 2025
2ada873
Move free-disk-space step before checkout in CI
maliming Oct 5, 2025
2750b3a
Add TickerQ dashboard integration to demo app
maliming Oct 5, 2025
185829f
Add priority support to TickerQ job configuration
maliming Oct 5, 2025
9c69d34
Add TickerQ job configuration and dashboard docs
maliming Oct 5, 2025
14c54c4
Refactor TickerQ managers and add worker configuration support
maliming Oct 5, 2025
91f7774
Rename jobType to workerType in options class
maliming Oct 5, 2025
ca4bd0e
Remove ExposeServices attribute from worker manager
maliming Oct 5, 2025
7149673
Optimize TickerQ background job and worker invocation
maliming Oct 6, 2025
c1b6557
Handling conflicts.
maliming Oct 6, 2025
11ca94f
Merge pull request #23879 from abpframework/auto-merge/rel-10-0/4016
maliming Oct 6, 2025
f40579b
article update
erdemcaygor Oct 6, 2025
6e9827e
Merge pull request #23880 from abpframework/auto-merge/rel-10-0/4017
maliming Oct 6, 2025
595e0ca
Merge pull request #23882 from abpframework/auto-merge/rel-10-0/4019
maliming Oct 6, 2025
f26af8d
Merge pull request #23885 from abpframework/auto-merge/rel-10-0/4020
maliming Oct 6, 2025
6aa0528
Update docs-nav.json
EngincanV Oct 6, 2025
76300f4
Fix path separator in solution file and improve docs
EngincanV Oct 6, 2025
31ee8b7
Merge pull request #23872 from abpframework/issue-#7069-localization
AlperenSamurlu Oct 6, 2025
64ba97f
Merge pull request #23810 from abpframework/article/building-dynamic-…
ebicoglu Oct 6, 2025
51c51ff
Merge pull request #23889 from abpframework/auto-merge/rel-10-0/4023
maliming Oct 6, 2025
648bdaf
Merge pull request #23890 from abpframework/auto-merge/rel-10-0/4024
maliming Oct 6, 2025
4918b63
Merge pull request #23893 from abpframework/auto-merge/rel-10-0/4025
maliming Oct 7, 2025
e8359d1
Remove manual installation steps from TickerQ docs
maliming Oct 7, 2025
98ff26d
Merge pull request #23802 from abpframework/copilot/fix-d5496b38-0efc…
maliming Oct 7, 2025
fc0257c
add: a new article for angular reusable components
sumeyyeKurtulus Oct 7, 2025
baa87d6
Merge pull request #23896 from abpframework/issue/reusable-components…
erdemcaygor Oct 7, 2025
38ebee7
Merge pull request #23869 from abpframework/CloseEmailSendModal
yagmurcelk Oct 7, 2025
620cc9f
Merge pull request #23901 from abpframework/auto-merge/rel-10-0/4026
maliming Oct 7, 2025
353579f
Merge pull request #23902 from abpframework/auto-merge/rel-10-0/4027
maliming Oct 7, 2025
a1bad4e
Merge pull request #23903 from abpframework/auto-merge/rel-10-0/4028
maliming Oct 7, 2025
f015044
Update en.json
ahmetcelik05 Oct 7, 2025
078753a
Optimised images with calibre/image-actions
github-actions[bot] Oct 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ jobs:
- uses: actions/setup-dotnet@master
with:
dotnet-version: 10.0.x

- name: Build All
run: ./build-all.ps1
working-directory: ./build
Expand Down
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="TencentCloudSDK.Sms" Version="3.0.1273" />
<PackageVersion Include="TimeZoneConverter" Version="7.0.0" />
<PackageVersion Include="TickerQ" Version="2.5.3" />
<PackageVersion Include="TickerQ.Dashboard" Version="2.5.3" />
<PackageVersion Include="TickerQ.Utilities" Version="2.5.3" />
<PackageVersion Include="TickerQ.EntityFrameworkCore" Version="2.5.3" />
<PackageVersion Include="Unidecode.NET" Version="2.1.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.9.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,13 @@
"Menu:Studio": "Studio",
"Menu:Solutions": "Solutions",
"Menu:Users": "Users",
"Menu:UserReports": "Users"
"Menu:UserReports": "Users",
"Enum:TokenType:1": "Free",
"Enum:TokenType:2": "Paid",
"Enum:SourceChannel:1": "Studio",
"Enum:SourceChannel:2": "Support Site",
"Enum:SourceChannel:3": "Suite",
"Menu:OrganizationTokenUsage": "Organization Token Usage",
"Permission:OrganizationTokenUsage": "Organization Token Usage"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@
"AbpConferenceDescription": "ABP Conference is a virtual event for .NET developers to learn and connect with the community.",
"Mobile": "Mobile",
"MetaTwitterCard": "summary_large_image",
"IPAddress": "IP Address"
"IPAddress": "IP Address",
"LicenseBanner:InfoText": "Your license will <b>expire in {0} days.</b>",
"LicenseBanner:CallToAction": "Please <a href=\"https://www.abp.io/my-organizations\" class=\"text-decoration-underline\">extend your license.</a>"
}
}
4 changes: 2 additions & 2 deletions common.props
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>10.0.0-rc.1</Version>
<LeptonXVersion>5.0.0-rc.1</LeptonXVersion>
<Version>10.1.0-preview</Version>
<LeptonXVersion>5.1.0-preview</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>
Expand Down
Original file line number Diff line number Diff line change
@@ -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:

![](blob-tables.png)

**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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading