diff --git a/docs/en/Blob-Storing-Custom-Provider.md b/docs/en/Blob-Storing-Custom-Provider.md new file mode 100644 index 0000000000..efd90c47f0 --- /dev/null +++ b/docs/en/Blob-Storing-Custom-Provider.md @@ -0,0 +1,3 @@ +# BLOB Storing: Creating a Custom Provider + +TODO \ No newline at end of file diff --git a/docs/en/Blob-Storing.md b/docs/en/Blob-Storing.md index 8c04e0661e..5ae1ce2f77 100644 --- a/docs/en/Blob-Storing.md +++ b/docs/en/Blob-Storing.md @@ -1,6 +1,6 @@ # BLOB Storing -It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. **[BLOB](https://en.wikipedia.org/wiki/Binary_large_object)** into a **storage**. For example, you may want to save user profile pictures. +It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. **[BLOB](https://en.wikipedia.org/wiki/Binary_large_object)**s, into a **storage**. For example, you may want to save user profile pictures. A BLOB is typically a **byte array**. Storing a BLOB in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/) can be an option. @@ -10,7 +10,9 @@ ABP Framework provides an abstraction to work with BLOBs and provides some pre-b * You can then **easily change** your BLOB storage without changing your application code. * If you want to create **reusable application modules**, you don't need to make assumption about how the BLOBs are stored. -### BLOB Storage Providers +ABP BLOB Storage system is also compatible to other ABP Framework features like [multi-tenancy](Multi-Tenancy.md). + +## BLOB Storage Providers ABP Framework has the following storage provider implementations; @@ -22,7 +24,7 @@ More providers will be implemented by the time. You can [request](https://github Multiple providers **can be used together** by the help of the **container system**, where each container can use a different provider (will be explained below). -> BLOB storing system can not work unless you **configure a storage provider**. Refer to the linked documents for the storage providers. +> BLOB storing system can not work unless you **configure a storage provider**. Refer to the linked documents for the storage provider configurations. ## Volo.Abp.BlobStoring Package @@ -65,7 +67,7 @@ namespace AbpDemo await _blobContainer.SaveAsync("my-blob-1", bytes); } - public async Task GetBytesAsync(byte[] bytes) + public async Task GetBytesAsync() { return await _blobContainer.GetAllBytesOrNullAsync("my-blob-1"); } @@ -77,16 +79,227 @@ This service saves given bytes with the `my-blob-1` name and then reads the prev > A BLOB is a named object and **each BLOB should have a unique name**, which is an arbitrary string. +`IBlobContainer` can work with `Stream` and `byte[]` objects, which will be detailed in the next sections. + #### Saving BLOBs -TODO +`SaveAsync` method is used to save a new BLOB or replace an existing BLOB. It can save a `Stream` by default, but there is a shortcut extension method to save byte arrays. + +`SaveAsync` gets the following parameters: + +* **name** (string): Unique name of the BLOB. +* **stream** (Stream) or **bytes** (byte[]): The stream to read the BLOB content or a byte array. +* **overrideExisting** (bool): Set `true` to replace the BLOB content if it does already exists. Default value is `false` and throws `BlobAlreadyExistsException` if there is already a BLOB in the container with the same name. #### Reading/Getting BLOBs +* `GetAsync`: Only gets a BLOB name and returns a `Stream` object that can be used to read the BLOB content. Always **dispose the stream** after using it. This method throws exception if can not find the BLOB with the given name. +* `GetOrNullAsync`: In opposite to the `GetAsync` method, this one returns `null` if there is no BLOB found with the given name. +* `GetAllBytesAsync`: Returns a `byte[]` instead of a `Stream`. Still throws exception if can not find the BLOB with the given name. +* `GetAllBytesOrNullAsync`: In opposite to the `GetAllBytesAsync` method, this one returns `null` if there is no BLOB found with the given name. + #### Deleting BLOBs +`DeleteAsync` method gets a BLOB name and deletes the BLOB data. It doesn't throw any exception if given BLOB was not found, instead it returns a `bool` indicates that the BLOB was actually deleted or not. + #### Other Methods -## Implementing Your Own BLOB Storage Provider +* `ExistsAsync` method simply checks if there is a saved BLOB with the given name. + +### Typed IBlobContainer + +Typed BLOB container is a way of creating and managing **multiple containers** in an application; + +* **Each container is separately stored**. That means BLOB names should be unique in a container and two BLOBs with the same name can live in different containers without effecting each other. +* **Each container can be separately configured**, so each container can use a different storage provider based on your configuration. + +To create a typed container, you need to create a simple class decorated with the `BlobContainerName` attribute: + +````csharp +using Volo.Abp.BlobStoring; + +namespace AbpDemo +{ + [BlobContainerName("profile-pictures")] + public class ProfilePictureContainer + { + + } +} +```` + +> If you don't use the `BlobContainerName` attribute, ABP Framework uses the full name of the class (with namespace), but it is always recommended to use a container name which is not changed even if you rename the class. + +Once you create the container class, you can inject `IBlobContainer` for your container type. + +**Example: An [application service](Application-Services.md) to save and read profile picture of the current user** + +````csharp +[Authorize] +public class ProfileAppService : ApplicationService +{ + private readonly IBlobContainer _blobContainer; + + public ProfileAppService(IBlobContainer blobContainer) + { + _blobContainer = blobContainer; + } + + public async Task SaveProfilePictureAsync(byte[] bytes) + { + var blobName = CurrentUser.GetId().ToString(); + await _blobContainer.SaveAsync(blobName, bytes); + } + + public async Task GetProfilePictureAsync() + { + var blobName = CurrentUser.GetId().ToString(); + return await _blobContainer.GetAllBytesOrNullAsync(blobName); + } +} +```` + +`IBlobContainer` has the same methods with the `IBlobContainer`. + +> It is a good practice to **always use a typed container while developing re-usable modules**, so the final application can configure the provider for your container without effecting the other containers. + +#### The Default Container + +If you don't use the generic argument and directly inject the `IBlobContainer` (as explained before), you get the default container. Another way of injecting the default container is like `IBlobContainer`, which returns exactly the same container. + +The name of the default container is `Default`. + +#### Named Containers + +Actually, types containers are just a shortcut for named containers. You can inject and use the `IBlobContainerFactory` to get a BLOB container by its name: + +````csharp +public class ProfileAppService : ApplicationService +{ + private readonly IBlobContainer _blobContainer; + + public ProfileAppService(IBlobContainerFactory blobContainerFactory) + { + _blobContainer = blobContainerFactory.Create("profile-pictures"); + } + + //... +} +```` + +### IBlobContainerFactory + +`IBlobContainerFactory` is the service that is used to create the BLOB containers. One example was shown above. + +**Example: Create a container by name** + +````csharp +var blobContainer = blobContainerFactory.Create("profile-pictures"); +```` + +**Example: Create a container by type** + +````csharp +var blobContainer = blobContainerFactory.Create(); +```` + +> You generally don't need to use the `IBlobContainerFactory` since it is used internally when you inject a `IBlobContainer` or `IBlobContainer`. + +### About Naming the BLOBs + +Naming BLOBs has not a forcing rule. A BLOB name is just a string that is unique per container. However, different storage providers may conventionally implement some practices. For example, the [File System Provider](Blob-Storing-File-System.md) use directory separators (`/`) and file extensions in your BLOB name (if your BLOB name is `images/common/x.png` then it is saved as `x.png` in the `images/common` folder of the root container folder). + +## Configuring the Containers + +Containers should be configured before using them. The most fundamental configuration is to **select a BLOB storage provider** (see the "*BLOB Storage Providers*" section above). + +`AbpBlobStoringOptions` is the [options class](Options.md) to configure the containers. You can configure the options inside the `ConfigureServices` method of your [module](Module-Development-Basics.md). + +### Configure a Single Container + +````csharp +Configure(options => +{ + options.Containers.Configure(container => + { + //TODO... + }); +}); +```` + +This example configures the `ProfilePictureContainer`. You can also configure by the container name: + +````csharp +Configure(options => +{ + options.Containers.Configure("profile-pictures", container => + { + //TODO... + }); +}); +```` + +### Configure the Default Container + +````csharp +Configure(options => +{ + options.Containers.ConfigureDefault(container => + { + //TODO... + }); +}); +```` + +> There is a special case about the default container; If you don't specify a configuration for a container, it **fallbacks to the default container configuration**. This is a good way to configure defaults for all containers and specialize configuration for a specific container when needed. + +### Configure All Containers + +````csharp +Configure(options => +{ + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + //TODO... + }); +}); +```` + +This is a way to configure all the containers. The main difference from configuring the default container is that you override the configuration even if it was specialized for a specific container. + +## Multi-Tenancy + +ABP BLOB Storing system **works silently with the [multi-tenancy](Multi-Tenancy.md)** if your application is multi-tenant. All the providers implement multi-tenancy as a standard feature. They **isolate BLOBs** of different tenants from each other, so they can only access to their own BLOBs. That also means you can use the **same BLOB name** for different tenants. + +If your application is multi-tenant, you may want to control **multi-tenancy behavior** of the containers individually. For example, you may want to **disable multi-tenancy** for a specific container, so the BLOBs inside it will be **available to all the tenants**. This is a way to share BLOBs among tenants. + +**Example: Disable multi-tenancy for a specific container** + +````csharp +Configure(options => +{ + options.Containers.Configure(container => + { + container.IsMultiTenant = false; + }); +}); +```` + +> If your application is not multi-tenant, no worry, it works as expected. You don't need to configure anything. + +## Extending the BLOB Storing System + +Most of the times, you won't need to customize the BLOB storage system except [creating a custom BLOB storage provider](Blob-Storing-Custom-Provider.md). However, you can replace any service introduced above (via [dependency injection](Dependency-Injection.md)), if you need. Here, some other services not mentioned above, but you may want to know: + +* `IBlobProviderSelector` is used to get a `IBlobProvider` instance by a container name. Default implementation (`DefaultBlobProviderSelector`) selects the provider using the configuration. +* `IBlobContainerConfigurationProvider` is used to get the `BlobContainerConfiguration` for a given container name. Default implementation (`DefaultBlobContainerConfigurationProvider`) gets the configuration from the `AbpBlobStoringOptions` explained above. + +## BLOB Storing vs File Management System + +Notice that BLOB storing is not a file management system. It is used to save, get and delete named BLOBs. It doesn't provide a hierarchical structure like directories, you may expect from a typical file system. BLOB Storing is a low level system. + +If you want to create folders and move files between folders, assign permissions to files and share files between users then you need to implement your own application on top of the BLOB Storing system. + +## See Also -TODO \ No newline at end of file +* [Creating a custom BLOB storage provider](Blob-Storing-Custom-Provider.md) \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 83fd02d137..548a89c2e3 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -179,6 +179,36 @@ "text": "Object to object mapping", "path": "Object-To-Object-Mapping.md" }, + { + "text": "BLOB Storing", + "items": [ + { + "text": "BLOB Storing System", + "path": "Blob-Storing.md" + }, + { + "text": "Storage Providers", + "items": [ + { + "text": "File System Provider", + "path": "Blob-Storing-File-System.md" + }, + { + "text": "Database Provider", + "path": "Blob-Storing-Database.md" + }, + { + "text": "Azure Provider", + "path": "Blob-Storing-Azure.md" + }, + { + "text": "Create a Custom Provider", + "path": "Blob-Storing-Custom-Provider.md" + } + ] + } + ] + }, { "text": "Text Templating", "path": "Text-Templating.md" diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs index 62f6aa7a73..ab27aab01f 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs @@ -1,5 +1,4 @@ -using System.Threading; -using Volo.Abp.DependencyInjection; +using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -27,7 +26,7 @@ namespace Volo.Abp.BlobStoring ProviderSelector = providerSelector; } - public virtual IBlobContainer Create(string name, CancellationToken cancellationToken = default) + public virtual IBlobContainer Create(string name) { var configuration = ConfigurationProvider.Get(name); diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs index 34483c1849..1dc51a2a72 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs @@ -1,6 +1,4 @@ -using System.Threading; - -namespace Volo.Abp.BlobStoring +namespace Volo.Abp.BlobStoring { public static class BlobContainerFactoryExtensions { @@ -13,13 +11,11 @@ namespace Volo.Abp.BlobStoring /// The container object. /// public static IBlobContainer Create( - this IBlobContainerFactory blobContainerFactory, - CancellationToken cancellationToken = default + this IBlobContainerFactory blobContainerFactory ) { return blobContainerFactory.Create( - BlobContainerNameAttribute.GetContainerName(), - cancellationToken + BlobContainerNameAttribute.GetContainerName() ); } } diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs index 64f907e564..038d53af6d 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs @@ -1,8 +1,8 @@ namespace Volo.Abp.BlobStoring { - [BlobContainerName("Default")] + [BlobContainerName(Name)] public class DefaultContainer { - + public const string Name = "Default"; } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs index f984a903a2..3dbeef3b10 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs @@ -2,6 +2,11 @@ { public interface IBlobContainerConfigurationProvider { + /// + /// Gets a for the given container . + /// + /// The name of the container + /// The configuration that should be used for the container BlobContainerConfiguration Get(string name); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs index 8434e3ae23..c9864fd378 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs @@ -1,6 +1,4 @@ -using System.Threading; - -namespace Volo.Abp.BlobStoring +namespace Volo.Abp.BlobStoring { public interface IBlobContainerFactory { @@ -8,13 +6,11 @@ namespace Volo.Abp.BlobStoring /// Gets a named container. /// /// The name of the container - /// Cancellation token /// /// The container object. /// IBlobContainer Create( - string name, - CancellationToken cancellationToken = default + string name ); } } \ No newline at end of file