@ -0,0 +1,94 @@ |
|||||
|
name: 🤠 ABP Studio |
||||
|
description: Create a report to help us improve the ABP Studio |
||||
|
labels: [studio] |
||||
|
body: |
||||
|
- type: markdown |
||||
|
attributes: |
||||
|
value: | |
||||
|
We welcome bug reports! This template will help us gather the information we need to start the triage process. |
||||
|
|
||||
|
Please keep in mind that the GitHub issue tracker is not intended as a general support forum, but for reporting **non-security** bugs and feature requests. |
||||
|
If you believe you have an issue that affects the SECURITY of the platform, please do NOT create an issue and instead email your issue details to info@abp.io. |
||||
|
For other types of questions, consider using [StackOverflow](https://stackoverflow.com/questions/tagged/abp). |
||||
|
- type: checkboxes |
||||
|
id: searched |
||||
|
attributes: |
||||
|
label: Is there an existing issue for this? |
||||
|
description: Please search to see if an issue already exists for the bug you encountered or feature request ([abp/issues](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+label%3Astudio)). |
||||
|
options: |
||||
|
- label: I have searched the existing issues |
||||
|
required: true |
||||
|
- type: textarea |
||||
|
id: background |
||||
|
attributes: |
||||
|
label: Description |
||||
|
description: Please share a clear and concise description of the problem. |
||||
|
placeholder: Description |
||||
|
validations: |
||||
|
required: true |
||||
|
- type: markdown |
||||
|
attributes: |
||||
|
value: | |
||||
|
## Setup |
||||
|
Please provide more information on your ABP Studio setup. |
||||
|
- type: input |
||||
|
id: version |
||||
|
attributes: |
||||
|
label: Version |
||||
|
description: Which version of ABP Studio are you using? |
||||
|
placeholder: Version |
||||
|
validations: |
||||
|
required: true |
||||
|
- type: dropdown |
||||
|
id: Operation-System |
||||
|
attributes: |
||||
|
label: Operation System |
||||
|
description: What is the operation system of the computer? |
||||
|
options: |
||||
|
- Windows (Default) |
||||
|
- Linux |
||||
|
- macOS |
||||
|
- Others |
||||
|
validations: |
||||
|
required: true |
||||
|
- type: textarea |
||||
|
id: solution-config |
||||
|
attributes: |
||||
|
label: Solution Configuration |
||||
|
description: | |
||||
|
If there is an open solution, what are the configurations of the solution? |
||||
|
🧐 Hint: You can see all the information about your solution from the configuration window, which opens when you right-click on the [solution](https://abp.io/docs/latest/studio/solution-explorer#solution) and click on the `Solution Configuration` button. |
||||
|
placeholder: | |
||||
|
- **Template**: app |
||||
|
- **Created ABP Studio Version**: 0.7.9 |
||||
|
- **Tiered**: No |
||||
|
- **UI Framework**: mvc |
||||
|
- **Theme**: leptonx |
||||
|
- **Theme Style**: system |
||||
|
- **Database Provider**: ef |
||||
|
- **Database Management System**: sqlserver |
||||
|
- **Separate Tenant Schema**: No |
||||
|
- **Mobile Framework**: none |
||||
|
- **Public Website**: No |
||||
|
- **Optional Modules**: |
||||
|
* GDPR |
||||
|
* TextTemplateManagement |
||||
|
* LanguageManagement |
||||
|
* AuditLogging |
||||
|
* SaaS |
||||
|
* OpenIddictAdmin |
||||
|
validations: |
||||
|
required: false |
||||
|
- type: markdown |
||||
|
attributes: |
||||
|
value: | |
||||
|
--- |
||||
|
- type: textarea |
||||
|
id: other-info |
||||
|
attributes: |
||||
|
label: Other information |
||||
|
description: | |
||||
|
If you have an idea where the problem might lie, let us know that here. Please include any pointers to code, relevant changes, or related issues you know of. |
||||
|
placeholder: Other information |
||||
|
validations: |
||||
|
required: false |
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 31 KiB |
@ -0,0 +1,70 @@ |
|||||
|
# BLOB Storing Google Provider |
||||
|
|
||||
|
BLOB Storing Google Provider can store BLOBs in [Google Cloud Storage](https://cloud.google.com/storage). |
||||
|
|
||||
|
> Read the [BLOB Storing document](../blob-storing) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Google Cloud Storage as the storage provider. |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
Use the ABP CLI to add [Volo.Abp.BlobStoring.Google](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Google) NuGet package to your project: |
||||
|
|
||||
|
* Install the [ABP CLI](../../../cli) if you haven't installed before. |
||||
|
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Google` package. |
||||
|
* Run `abp add-package Volo.Abp.BlobStoring.Google` command. |
||||
|
|
||||
|
If you want to do it manually, install the [Volo.Abp.BlobStoring.Google](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Google) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringGoogleModule))]` to the [ABP module](../../architecture/modularity/basics.md) class inside your project. |
||||
|
|
||||
|
## Configuration |
||||
|
|
||||
|
Configuration is done in the `ConfigureServices` method of your [module](../../architecture/modularity/basics.md) class, as explained in the [BLOB Storing document](../blob-storing). |
||||
|
|
||||
|
**Example: Configure to use the Google storage provider by default** |
||||
|
|
||||
|
````csharp |
||||
|
Configure<AbpBlobStoringOptions>(options => |
||||
|
{ |
||||
|
options.Containers.ConfigureDefault(container => |
||||
|
{ |
||||
|
container.UseGoogle(google => |
||||
|
{ |
||||
|
google.ClientEmail = "your coogle client email"; |
||||
|
google.ProjectId = "your coogle project id"; |
||||
|
google.PrivateKey = "your coogle private key"; |
||||
|
google.Scopes = "your coogle scopes"; |
||||
|
google.ContainerName = "your coogle container name"; |
||||
|
google.CreateContainerIfNotExists = true; |
||||
|
//google.UseApplicationDefaultCredentials = true; // If you want to use application default credentials |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
> See the [BLOB Storing document](../blob-storing) to learn how to configure this provider for a specific container. |
||||
|
|
||||
|
### Options |
||||
|
|
||||
|
* **ClientEmail** (string): The client email of the Google service account. You can create a service account and get the client email. Please refer to Google documentation: https://cloud.google.com/iam/docs/service-account-overview |
||||
|
* **ProjectId** (string): The project ID of the Google service account. |
||||
|
* **PrivateKey** (string): The private key of the Google service account. You can create a service account and get the private key. Please refer to Google documentation: https://cloud.google.com/iam/docs/keys-create-delete |
||||
|
* **Scopes** (string): The scopes of the Google service account. |
||||
|
* **UseApplicationDefaultCredentials** (bool): If `true`, it uses the application default credentials(ADC). Default value is `false`. Please refer to Google documentation: https://cloud.google.com/docs/authentication/provide-credentials-adc |
||||
|
* **ContainerName** (string): You can specify the container name in Google. If this is not specified, it uses the name of the BLOB container defined with the `BlobContainerName` attribute (see the [BLOB storing document](../blob-storing)). Please note that Google has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names): |
||||
|
* Container names must start or end with a letter or number, and can contain only letters, numbers, and the dash (-) character. |
||||
|
* Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names. |
||||
|
* All letters in a container name must be **lowercase**. |
||||
|
* Container names must be from **3** through **63** characters long. |
||||
|
* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Google, `GoogleBlobProvider` will try to create it. |
||||
|
|
||||
|
|
||||
|
## Google Blob Name Calculator |
||||
|
|
||||
|
Google Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: |
||||
|
|
||||
|
* Appends `host` string if [current tenant](../../architecture/multi-tenancy) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](../blob-storing) to learn how to disable multi-tenancy for a container). |
||||
|
* Appends `tenants/<tenant-id>` string if current tenant is not `null`. |
||||
|
* Appends the BLOB name. |
||||
|
|
||||
|
## Other Services |
||||
|
|
||||
|
* `GoogleBlobProvider` is the main service that implements the Google BLOB storage provider, if you want to override/replace it via [dependency injection](../../fundamentals/dependency-injection.md) (don't replace `IBlobProvider` interface, but replace `GoogleBlobProvider` class). |
||||
|
* `IGoogleBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultGoogleBlobNameCalculator` by default. |
||||
|
After Width: | Height: | Size: 54 KiB |
@ -0,0 +1,91 @@ |
|||||
|
# ABP Studio Release Notes |
||||
|
|
||||
|
This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version. |
||||
|
|
||||
|
## 0.7.9 (2024-08-29) |
||||
|
|
||||
|
* Opened Readme.md file after module creation or opening a solution |
||||
|
* Refactor & made enhancements to microservice template |
||||
|
* Fixed some bugs that occur while creating a new module |
||||
|
* Synchronized new templates with the old templates |
||||
|
* Angular: Fixed logo problem in templates |
||||
|
|
||||
|
## 0.7.8 (2024-08-23) |
||||
|
|
||||
|
* Revised the new solution creation wizard and improved for UX |
||||
|
* Fixed bugs on the Blazor WebApp template |
||||
|
* Reduced logs count per application |
||||
|
|
||||
|
## 0.7.7 (2024-08-14) |
||||
|
|
||||
|
* Updated LeptonX logos in new templates |
||||
|
* ABP Suite: Opened modules with .sln path instead of directory (fixes some problems in Suite integration) |
||||
|
* Enhancements on Solution Runner |
||||
|
|
||||
|
## 0.7.6 (2024-08-12) |
||||
|
|
||||
|
* Removed redundant helm repo installation |
||||
|
* Made enhancements to the Welcome Page area |
||||
|
* Microservice Template: Enabled `DynamicPermissionStore` in AuthServer |
||||
|
* Added Blazor WebApp option for Application template |
||||
|
* Added **Solution Creation Info** action for identifying the solution |
||||
|
|
||||
|
## 0.7.5 (2024-08-06) |
||||
|
|
||||
|
* Fixed ABP Suite integration for macOS |
||||
|
* Fixed build errors after upgrading to ABP 8.2.1 |
||||
|
* ABP Suite: Detected version inconsistencies and notified users to fix them automatically |
||||
|
|
||||
|
## 0.7.4 (2024-07-31) |
||||
|
|
||||
|
* Allowed creating open-source templates for active license owners |
||||
|
* Fixed basic theme problems in angular templates |
||||
|
* Handled docker related errors in solution runner |
||||
|
* Fixed versions of Chat Module packages |
||||
|
|
||||
|
## 0.7.3 (2024-07-27) |
||||
|
|
||||
|
* Added administrator mode indicator on the application title |
||||
|
* Made enhancements for aligning application title on macOS |
||||
|
|
||||
|
## 0.7.2 (2024-07-26) |
||||
|
|
||||
|
* Added `Blazor.Client` packages for alignment with new hosting logic |
||||
|
* Updated Microsoft packages to v8.0.4 |
||||
|
* Revised application upgrade process |
||||
|
* Fixed profile photo problem in tiered projects |
||||
|
* Removed **LinkedAccounts** and **AuthorityDelegation** menu from templates |
||||
|
|
||||
|
## 0.7.1 (2024-07-24) |
||||
|
|
||||
|
* Fixed restart problem after installing extensions on macOS |
||||
|
* Provided a model for sharing the same Kubernetes cluster between developers |
||||
|
* Fixed the **Text Template Management** Module build problem in the app template |
||||
|
* Fixed login problem for the community edition |
||||
|
* Fixed all reported bugs & made enhancements |
||||
|
|
||||
|
## 0.7.0 (2024-07-17) |
||||
|
|
||||
|
* Added an option for building & starting C# applications directly |
||||
|
* Disabled redis on migrator when the solution is tiered |
||||
|
* Allowed Generating Static Proxies on ABP Studio UI |
||||
|
|
||||
|
## 0.6.9 (2024-06-27) |
||||
|
|
||||
|
* Allowed to open ABP Studio by clicking on a `*.abpsln` file |
||||
|
* Fixed solution runner multiple instance problem |
||||
|
* Allowed to install an NPM package to a package |
||||
|
|
||||
|
## 0.6.8 (2024-06-13) |
||||
|
|
||||
|
* Added free to pro upgrade option |
||||
|
* Fixed microservice template helm chart bugs |
||||
|
* Made enhancements to the new CLI |
||||
|
|
||||
|
## 0.6.7 (2024-05-31) |
||||
|
|
||||
|
* Added free option to app templates |
||||
|
* Made social login optional for the app template |
||||
|
* Added open-source (free) templates |
||||
|
* Started showing angular projects on the browser tab |
||||
|
* Introduced the Community Edition |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<WarningsAsErrors>Nullable</WarningsAsErrors> |
||||
|
<AssemblyName>Volo.Abp.BlobStoring.Google</AssemblyName> |
||||
|
<PackageId>Volo.Abp.BlobStoring.Google</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" /> |
||||
|
<PackageReference Include="Google.Cloud.Storage.V1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
[DependsOn(typeof(AbpBlobStoringModule))] |
||||
|
public class AbpBlobStoringGoogleModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class DefaultGoogleBlobNameCalculator : IGoogleBlobNameCalculator, ITransientDependency |
||||
|
{ |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
|
||||
|
public DefaultGoogleBlobNameCalculator(ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
CurrentTenant = currentTenant; |
||||
|
} |
||||
|
|
||||
|
public virtual string Calculate(BlobProviderArgs args) |
||||
|
{ |
||||
|
return CurrentTenant.Id == null |
||||
|
? $"host/{args.BlobName}" |
||||
|
: $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public static class GoogleBlobContainerConfigurationExtensions |
||||
|
{ |
||||
|
public static GoogleBlobProviderConfiguration GetGoogleConfiguration( |
||||
|
this BlobContainerConfiguration containerConfiguration) |
||||
|
{ |
||||
|
return new GoogleBlobProviderConfiguration(containerConfiguration); |
||||
|
} |
||||
|
|
||||
|
public static BlobContainerConfiguration UseGoogle( |
||||
|
this BlobContainerConfiguration containerConfiguration, |
||||
|
Action<GoogleBlobProviderConfiguration> googleConfigureAction) |
||||
|
{ |
||||
|
containerConfiguration.ProviderType = typeof(GoogleBlobProvider); |
||||
|
containerConfiguration.NamingNormalizers.TryAdd<GoogleBlobNamingNormalizer>(); |
||||
|
|
||||
|
googleConfigureAction(new GoogleBlobProviderConfiguration(containerConfiguration)); |
||||
|
|
||||
|
return containerConfiguration; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,120 @@ |
|||||
|
using System.Globalization; |
||||
|
using System.Text.RegularExpressions; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class GoogleBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// https://cloud.google.com/storage/docs/buckets#naming
|
||||
|
/// </summary>
|
||||
|
public string NormalizeContainerName(string containerName) |
||||
|
{ |
||||
|
using (CultureHelper.Use(CultureInfo.InvariantCulture)) |
||||
|
{ |
||||
|
// All letters in a Bucket name must be lowercase.
|
||||
|
containerName = containerName.ToLower(); |
||||
|
|
||||
|
// Bucket names must contain 3-63 characters. Names containing dots can contain up to 222 characters, but each dot-separated component can be no longer than 63 characters.
|
||||
|
if(containerName.Contains(".")) |
||||
|
{ |
||||
|
if (containerName.Length > 222) |
||||
|
{ |
||||
|
containerName = containerName.Substring(0, 222); |
||||
|
} |
||||
|
|
||||
|
var parts = containerName.Split('.'); |
||||
|
for (var i = 0; i < parts.Length; i++) |
||||
|
{ |
||||
|
if (parts[i].Length > 63) |
||||
|
{ |
||||
|
parts[i] = parts[i].Substring(0, 63); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
containerName = string.Join(".", parts); |
||||
|
} |
||||
|
else if (containerName.Length > 63) |
||||
|
{ |
||||
|
containerName = containerName.Substring(0, 63); |
||||
|
} |
||||
|
|
||||
|
//Bucket names can only contain lowercase letters, numeric characters, dashes (-), underscores (_), and dots (.). Spaces are not allowed. Names containing dots require verification.
|
||||
|
containerName = Regex.Replace(containerName, "[^a-z0-9-_.]", string.Empty); |
||||
|
|
||||
|
//Be a syntactically valid DNS name (for example, bucket..example.com is not valid because it contains two dots in a row).
|
||||
|
containerName = Regex.Replace(containerName, "[.]{2,}", "."); |
||||
|
|
||||
|
//Bucket names cannot be represented as an IP address in dotted-decimal notation (for example, 192.168.5.4).
|
||||
|
containerName = Regex.Replace(containerName, "^(?:(?:^|\\.)(?:2(?:5[0-5]|[0-4]\\d)|1?\\d?\\d)){4}$", string.Empty); |
||||
|
|
||||
|
//Bucket names cannot begin with the "goog" prefix.
|
||||
|
containerName = Regex.Replace(containerName, "^goog", string.Empty); |
||||
|
|
||||
|
//Bucket names cannot contain "google" or close misspellings, such as "g00gle".
|
||||
|
containerName = Regex.Replace(containerName, "google", string.Empty); |
||||
|
|
||||
|
//Bucket names must start and end with a number or letter.
|
||||
|
containerName = RemoveInvalidStartEndCharacters(containerName); |
||||
|
|
||||
|
// Bucket names must be from 3 through 63 characters long. Names containing dots can contain up to 222 characters.
|
||||
|
if (containerName.Length < 3) |
||||
|
{ |
||||
|
var length = containerName.Length; |
||||
|
for (var i = 0; i < 3 - length; i++) |
||||
|
{ |
||||
|
containerName += "0"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return containerName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual string RemoveInvalidStartEndCharacters(string containerName) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(containerName)) |
||||
|
{ |
||||
|
return containerName; |
||||
|
} |
||||
|
|
||||
|
if (!char.IsLetterOrDigit(containerName[0])) |
||||
|
{ |
||||
|
containerName = containerName.Substring(1); |
||||
|
return RemoveInvalidStartEndCharacters(containerName); |
||||
|
} |
||||
|
|
||||
|
if (!char.IsLetterOrDigit(containerName[containerName.Length - 1])) |
||||
|
{ |
||||
|
containerName = containerName.Substring(0, containerName.Length - 1); |
||||
|
return RemoveInvalidStartEndCharacters(containerName); |
||||
|
} |
||||
|
|
||||
|
return containerName; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// https://cloud.google.com/storage/docs/objects#naming
|
||||
|
/// </summary>
|
||||
|
public string NormalizeBlobName(string blobName) |
||||
|
{ |
||||
|
//Object names can contain any sequence of valid Unicode characters, of length 1-1024 bytes when UTF-8 encoded
|
||||
|
if (blobName.Length > 1024) |
||||
|
{ |
||||
|
blobName = blobName.Substring(0, 1024); |
||||
|
} |
||||
|
|
||||
|
//Object names cannot contain Carriage Return or Line Feed characters.
|
||||
|
blobName = Regex.Replace(blobName, "[\r\n]", string.Empty); |
||||
|
|
||||
|
//Object names cannot start with .well-known/acme-challenge/.
|
||||
|
blobName = Regex.Replace(blobName, "^\\.well-known/acme-challenge/", string.Empty); |
||||
|
|
||||
|
//Objects cannot be named . or ...
|
||||
|
blobName = Regex.Replace(blobName, "^\\.\\.?$", string.Empty); |
||||
|
|
||||
|
return blobName; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Net; |
||||
|
using System.Threading.Tasks; |
||||
|
using Google; |
||||
|
using Google.Apis.Auth.OAuth2; |
||||
|
using Google.Cloud.Storage.V1; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class GoogleBlobProvider : BlobProviderBase, ITransientDependency |
||||
|
{ |
||||
|
protected IGoogleBlobNameCalculator GoogleBlobNameCalculator { get; } |
||||
|
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } |
||||
|
|
||||
|
public GoogleBlobProvider(IGoogleBlobNameCalculator googleBlobNameCalculator, IBlobNormalizeNamingService blobNormalizeNamingService) |
||||
|
{ |
||||
|
GoogleBlobNameCalculator = googleBlobNameCalculator; |
||||
|
BlobNormalizeNamingService = blobNormalizeNamingService; |
||||
|
} |
||||
|
|
||||
|
public async override Task SaveAsync(BlobProviderSaveArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetGoogleConfiguration(); |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
var blobName = GoogleBlobNameCalculator.Calculate(args); |
||||
|
var containerName = GetContainerName(args); |
||||
|
|
||||
|
if (await BlobExistsAsync(args, blobName) && !args.OverrideExisting) |
||||
|
{ |
||||
|
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{GetContainerName(args)}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); |
||||
|
} |
||||
|
|
||||
|
if (configuration.CreateContainerIfNotExists) |
||||
|
{ |
||||
|
await CreateContainerIfNotExists(args); |
||||
|
} |
||||
|
|
||||
|
await storageClient.UploadObjectAsync(containerName, blobName, contentType: "application/octet-stream", args.BlobStream); |
||||
|
} |
||||
|
|
||||
|
public async override Task<bool> DeleteAsync(BlobProviderDeleteArgs args) |
||||
|
{ |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
var blobName = GoogleBlobNameCalculator.Calculate(args); |
||||
|
var containerName = GetContainerName(args); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await storageClient.DeleteObjectAsync(containerName, blobName); |
||||
|
} |
||||
|
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public async override Task<bool> ExistsAsync(BlobProviderExistsArgs args) |
||||
|
{ |
||||
|
var blobName = GoogleBlobNameCalculator.Calculate(args); |
||||
|
return await BlobExistsAsync(args, blobName); |
||||
|
} |
||||
|
|
||||
|
public async override Task<Stream?> GetOrNullAsync(BlobProviderGetArgs args) |
||||
|
{ |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
var blobName = GoogleBlobNameCalculator.Calculate(args); |
||||
|
var containerName = GetContainerName(args); |
||||
|
|
||||
|
if(!await BlobExistsAsync(args, blobName)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var stream = new MemoryStream(); |
||||
|
|
||||
|
await storageClient.DownloadObjectAsync(containerName, blobName, stream); |
||||
|
|
||||
|
stream.Seek(0, SeekOrigin.Begin); |
||||
|
|
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetContainerName(BlobProviderArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetGoogleConfiguration(); |
||||
|
return configuration.ContainerName.IsNullOrWhiteSpace() |
||||
|
? args.ContainerName |
||||
|
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName!); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName) |
||||
|
{ |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
if(!await ContainerExistsAsync(args, storageClient)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await storageClient.GetObjectAsync(GetContainerName(args), blobName); |
||||
|
} |
||||
|
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task CreateContainerIfNotExists(BlobProviderArgs args) |
||||
|
{ |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
var configuration = args.Configuration.GetGoogleConfiguration(); |
||||
|
if(await ContainerExistsAsync(args, storageClient)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await storageClient.CreateBucketAsync(configuration.ProjectId, GetContainerName(args)); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<bool> ContainerExistsAsync(BlobProviderArgs args, StorageClient client) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await client.GetBucketAsync(GetContainerName(args)); |
||||
|
} |
||||
|
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<StorageClient> GetStorageClientClientAsync(BlobProviderArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetGoogleConfiguration(); |
||||
|
if (configuration.UseApplicationDefaultCredentials) |
||||
|
{ |
||||
|
return await StorageClient.CreateAsync(); |
||||
|
} |
||||
|
|
||||
|
var googleCredential = GoogleCredential.FromServiceAccountCredential( |
||||
|
new ServiceAccountCredential( |
||||
|
new ServiceAccountCredential.Initializer(configuration.ClientEmail) |
||||
|
{ |
||||
|
ProjectId = configuration.ProjectId, |
||||
|
Scopes = configuration.Scopes |
||||
|
} |
||||
|
.FromPrivateKey(configuration.PrivateKey) |
||||
|
)); |
||||
|
|
||||
|
return await StorageClient.CreateAsync(googleCredential); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class GoogleBlobProviderConfiguration |
||||
|
{ |
||||
|
private readonly BlobContainerConfiguration _containerConfiguration; |
||||
|
|
||||
|
public GoogleBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) |
||||
|
{ |
||||
|
_containerConfiguration = containerConfiguration; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unique identifier for your project.
|
||||
|
/// For more info see: https://cloud.google.com/resource-manager/docs/creating-managing-projects
|
||||
|
/// </summary>
|
||||
|
public string? ProjectId { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ProjectId); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ProjectId, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Email address that generated by the Google Cloud.
|
||||
|
/// </summary>
|
||||
|
public string? ClientEmail { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ClientEmail); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ClientEmail, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Private key that generated by Google Cloud.
|
||||
|
/// Starts with '-----BEGIN PRIVATE KEY-----'
|
||||
|
/// and ends with '-----END PRIVATE KEY-----'
|
||||
|
/// </summary>
|
||||
|
public string? PrivateKey { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.PrivateKey); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.PrivateKey, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Available OAuth 2.0 scopes.
|
||||
|
/// </summary>
|
||||
|
public List<string>? Scopes { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault(GoogleBlobProviderConfigurationNames.Scopes, new List<string>()); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.Scopes, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Use the application default credentials. https://cloud.google.com/docs/authentication/provide-credentials-adc
|
||||
|
/// Default value: false.
|
||||
|
/// </summary>
|
||||
|
public bool UseApplicationDefaultCredentials { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault(GoogleBlobProviderConfigurationNames.UseApplicationDefaultCredentials, false); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.UseApplicationDefaultCredentials, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The name can only contain lowercase letters, numeric characters, dashes (-), underscores (_), and dots (.). Spaces are not allowed. Names containing dots require verification.
|
||||
|
/// Must start and end with a number or letter.
|
||||
|
/// Must contain 3-63 characters. Names containing dots can contain up to 222 characters, but each dot-separated component can be no longer than 63 characters.
|
||||
|
/// Cannot be represented as an IP address in dotted-decimal notation (for example, 192.168.5.4).
|
||||
|
/// Cannot begin with the "goog" prefix.
|
||||
|
/// Cannot contain "google" or close misspellings, such as "g00gle".
|
||||
|
/// If this parameter is not specified, the ContainerName of the <see cref="BlobProviderArgs"/> will be used.
|
||||
|
/// </summary>
|
||||
|
public string? ContainerName { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ContainerName); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ContainerName, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: false.
|
||||
|
/// </summary>
|
||||
|
public bool CreateContainerIfNotExists { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault(GoogleBlobProviderConfigurationNames.CreateContainerIfNotExists, false); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.CreateContainerIfNotExists, value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public static class GoogleBlobProviderConfigurationNames |
||||
|
{ |
||||
|
public const string ProjectId = "Google.ProjectId"; |
||||
|
public const string ClientEmail = "Google.ClientEmail"; |
||||
|
public const string PrivateKey = "Google.PrivateKey"; |
||||
|
public const string Scopes = "Google.Scopes"; |
||||
|
public const string UseApplicationDefaultCredentials = "Google.ApplicationDefaultCredentials"; |
||||
|
public const string ContainerName = "Google.ContainerName"; |
||||
|
public const string CreateContainerIfNotExists = "Google.CreateContainerIfNotExists"; |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public interface IGoogleBlobNameCalculator |
||||
|
{ |
||||
|
string Calculate(BlobProviderArgs args); |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.test.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net8.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<UserSecretsId>9f0d2c00-80c1-435b-bfab-2c39c8249091</UserSecretsId> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.BlobStoring.Google\Volo.Abp.BlobStoring.Google.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
||||
|
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Volo.Abp.Testing; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class AbpBlobStoringGoogleTestCommonBase : AbpIntegratedTest<AbpBlobStoringGoogleTestCommonModule> |
||||
|
{ |
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class AbpBlobStoringGoogleTestBase : AbpIntegratedTest<AbpBlobStoringGoogleTestModule> |
||||
|
{ |
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Google.Apis.Auth.OAuth2; |
||||
|
using Google.Cloud.Storage.V1; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This module will not try to connect to Google Cloud Storage.
|
||||
|
/// </summary>
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringGoogleModule), |
||||
|
typeof(AbpBlobStoringTestModule) |
||||
|
)] |
||||
|
public class AbpBlobStoringGoogleTestCommonModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringGoogleTestCommonModule) |
||||
|
)] |
||||
|
public class AbpBlobStoringGoogleTestModule : AbpModule |
||||
|
{ |
||||
|
private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; |
||||
|
|
||||
|
private string _clientEmail; |
||||
|
private string _projectId; |
||||
|
private string _privateKey; |
||||
|
|
||||
|
private readonly string _randomContainerName = "abp-test-container-" + Guid.NewGuid().ToString("N"); |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => |
||||
|
{ |
||||
|
builder.AddUserSecrets(UserSecretsId); |
||||
|
})); |
||||
|
|
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
_clientEmail = configuration["Google:ClientEmail"]; |
||||
|
_projectId = configuration["Google:ProjectId"]; |
||||
|
_privateKey = configuration["Google:PrivateKey"]; |
||||
|
|
||||
|
Configure<AbpBlobStoringOptions>(options => |
||||
|
{ |
||||
|
options.Containers.ConfigureAll((containerName, containerConfiguration) => |
||||
|
{ |
||||
|
containerConfiguration.UseGoogle(google => |
||||
|
{ |
||||
|
google.ClientEmail = _clientEmail; |
||||
|
google.ProjectId = _projectId; |
||||
|
google.PrivateKey = _privateKey; |
||||
|
google.ContainerName = _randomContainerName; |
||||
|
google.CreateContainerIfNotExists = true; |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void OnApplicationShutdown(ApplicationShutdownContext context) |
||||
|
{ |
||||
|
var googleCredential = GoogleCredential.FromServiceAccountCredential( |
||||
|
new ServiceAccountCredential( |
||||
|
new ServiceAccountCredential.Initializer(_clientEmail) |
||||
|
{ |
||||
|
ProjectId = _projectId |
||||
|
} |
||||
|
.FromPrivateKey(_privateKey) |
||||
|
)); |
||||
|
|
||||
|
var client = StorageClient.Create(googleCredential); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
client.DeleteBucket(_randomContainerName, new DeleteBucketOptions |
||||
|
{ |
||||
|
DeleteObjects = true |
||||
|
}); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
// ignored
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class DefaultGoogleBlobNamingNormalizerProvider_Tests: AbpBlobStoringGoogleTestCommonBase |
||||
|
{ |
||||
|
private readonly IBlobNamingNormalizer _blobNamingNormalizer; |
||||
|
|
||||
|
public DefaultGoogleBlobNamingNormalizerProvider_Tests() |
||||
|
{ |
||||
|
_blobNamingNormalizer = GetRequiredService<IBlobNamingNormalizer>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Lowercase() |
||||
|
{ |
||||
|
var filename = "ThisIsMyContainerName"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("thisismycontainername"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Only_Letters_Numbers_Dash_Dots_Underscores() |
||||
|
{ |
||||
|
var filename = ",./this-i,/s-my-c,/ont,/ai+*/=!@#$n^&*er.name+/"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this-is-my-container.name"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Only_Start_With_Letters_Or_Numbers() |
||||
|
{ |
||||
|
var filename = "-this.--is-.-.-my--_container---name-"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this.--is-.-.-my--_container---name"); |
||||
|
|
||||
|
filename = ".this.--is-.-.-my--container---name0._"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this.--is-.-.-my--container---name0"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Min_Length() |
||||
|
{ |
||||
|
var filename = "a"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.Length.ShouldBeGreaterThanOrEqualTo(3); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Max_Length() |
||||
|
{ |
||||
|
var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.Length.ShouldBeLessThanOrEqualTo(63); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Must_Not_Be_Ip_Address() |
||||
|
{ |
||||
|
var filename = "192.168.5.4"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("000"); |
||||
|
|
||||
|
filename = "a.192.168.5.4"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("a.192.168.5.4"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Dots() |
||||
|
{ |
||||
|
var filename = ".this..is.my.container....name."; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this.is.my.container.name"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_DNS() |
||||
|
{ |
||||
|
var filename = "bucket...example..com"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("bucket.example.com"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Max_Length_Dash() |
||||
|
{ |
||||
|
var filename = "-this-is-my-container-name-abpabpabpabpabpabpabpabp-a-b-p-a--b-p-"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this-is-my-container-name-abpabpabpabpabpabpabpabp-a-b-p-a--b"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
/* |
||||
|
//Please set the correct connection string in secrets.json and continue the test.
|
||||
|
public class GoogleBlobContainer_Tests : BlobContainer_Tests<AbpBlobStoringGoogleTestModule> |
||||
|
{ |
||||
|
public GoogleBlobContainer_Tests() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
*/ |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class GoogleBlobNameCalculator_Tests : AbpBlobStoringGoogleTestCommonBase |
||||
|
{ |
||||
|
private readonly IGoogleBlobNameCalculator _calculator; |
||||
|
private readonly ICurrentTenant _currentTenant; |
||||
|
|
||||
|
private const string GoogleContainerName = "/"; |
||||
|
private const string GoogleSeparator = "/"; |
||||
|
|
||||
|
public GoogleBlobNameCalculator_Tests() |
||||
|
{ |
||||
|
_calculator = GetRequiredService<IGoogleBlobNameCalculator>(); |
||||
|
_currentTenant = GetRequiredService<ICurrentTenant>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Default_Settings() |
||||
|
{ |
||||
|
_calculator.Calculate( |
||||
|
GetArgs("my-container", "my-blob") |
||||
|
).ShouldBe($"host{GoogleSeparator}my-blob"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Default_Settings_With_TenantId() |
||||
|
{ |
||||
|
var tenantId = Guid.NewGuid(); |
||||
|
|
||||
|
using (_currentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
_calculator.Calculate( |
||||
|
GetArgs("my-container", "my-blob") |
||||
|
).ShouldBe($"tenants{GoogleSeparator}{tenantId:D}{GoogleSeparator}my-blob"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static BlobProviderArgs GetArgs( |
||||
|
string containerName, |
||||
|
string blobName) |
||||
|
{ |
||||
|
return new BlobProviderGetArgs( |
||||
|
containerName, |
||||
|
new BlobContainerConfiguration().UseGoogle(x => |
||||
|
{ |
||||
|
x.ContainerName = containerName; |
||||
|
}), |
||||
|
blobName |
||||
|
); |
||||
|
} |
||||
|
} |
||||