14 changed files with 362 additions and 1 deletions
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,29 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.BlobStoring</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.BlobStoring</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<Content Remove="LINGYUN\Abp\ElsaNext\BlobStoring\Localization\**\*.json" /> |
|||
<EmbeddedResource Include="LINGYUN\Abp\ElsaNext\BlobStoring\Localization\**\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.BlobStoring" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,38 @@ |
|||
using Elsa.Features.Services; |
|||
using LINGYUN.Abp.ElsaNext.BlobStoring.Extensions; |
|||
using LINGYUN.Abp.ElsaNext.Localization; |
|||
using Volo.Abp.BlobStoring; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.BlobStoring; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpBlobStoringModule), |
|||
typeof(AbpElsaNextModule))] |
|||
public class AbpElsaNextBlobStoringModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<IModule>(elsa => |
|||
{ |
|||
elsa.UseAbpBlobStoring(); |
|||
}); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpElsaNextBlobStoringModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<ElsaNextResource>() |
|||
.AddVirtualJson("/LINGYUN/Abp/ElsaNext/BlobStoring/Localization/Resources"); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using Elsa.Workflows; |
|||
using Elsa.Workflows.Attributes; |
|||
using Elsa.Workflows.Models; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BlobStoring; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
|||
|
|||
public abstract class BlobActivity : CodeActivity |
|||
{ |
|||
[Input(Description = "Path of the blob.")] |
|||
public Input<string> Path { get; set; } = default!; |
|||
|
|||
protected virtual IBlobContainer<ElsaBlobContainer> GetBlobContainer(ActivityExecutionContext context) |
|||
{ |
|||
return context.GetRequiredService<IBlobContainer<ElsaBlobContainer>>(); |
|||
} |
|||
|
|||
protected async virtual ValueTask OnErrorCompletedAsync(ActivityCompletedContext context) |
|||
{ |
|||
await context.TargetContext.CompleteActivityAsync(); |
|||
} |
|||
} |
|||
|
|||
public abstract class BlobActivity<TResult> : CodeActivity<TResult> |
|||
{ |
|||
[Input(Description = "Path of the blob.")] |
|||
public Input<string> Path { get; set; } = default!; |
|||
|
|||
protected virtual IBlobContainer<ElsaBlobContainer> GetBlobContainer(ActivityExecutionContext context) |
|||
{ |
|||
return context.GetRequiredService<IBlobContainer<ElsaBlobContainer>>(); |
|||
} |
|||
|
|||
protected async virtual ValueTask OnErrorCompletedAsync(ActivityCompletedContext context) |
|||
{ |
|||
await context.TargetContext.CompleteActivityAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using Elsa.Extensions; |
|||
using Elsa.Workflows; |
|||
using Elsa.Workflows.Attributes; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
|||
|
|||
[Activity("Elsa", "BlobStoring", "Delete a blob.", Kind = ActivityKind.Task)] |
|||
public class DeleteBlob : BlobActivity<bool> |
|||
{ |
|||
protected async override ValueTask ExecuteAsync(ActivityExecutionContext context) |
|||
{ |
|||
var cancellationToken = context.CancellationToken; |
|||
var path = Path.Get(context); |
|||
var blobContainer = GetBlobContainer(context); |
|||
var result = await blobContainer.DeleteAsync(path, cancellationToken); |
|||
|
|||
Result.Set(context, result); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using Elsa.Extensions; |
|||
using Elsa.Workflows; |
|||
using Elsa.Workflows.Attributes; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
|||
|
|||
[Activity("Elsa", "BlobStoring", "Open a blob.", Kind = ActivityKind.Task)] |
|||
public class ReadBlob : BlobActivity<Stream> |
|||
{ |
|||
[Port] public IActivity? Error { get; set; } |
|||
|
|||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) |
|||
{ |
|||
var cancellationToken = context.CancellationToken; |
|||
var path = Path.Get(context); |
|||
var blobContainer = GetBlobContainer(context); |
|||
|
|||
var logger = context.GetRequiredService<ILogger<ReadBlob>>(); |
|||
try |
|||
{ |
|||
var data = await blobContainer.GetAsync(path, cancellationToken); |
|||
|
|||
Result.Set(context, data); |
|||
await context.CompleteActivityAsync(); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
logger.LogWarning(e, "Error while open a blob."); |
|||
context.AddExecutionLogEntry("Error", e.Message, payload: new |
|||
{ |
|||
e.StackTrace |
|||
}); |
|||
await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
using Elsa.Extensions; |
|||
using Elsa.Workflows; |
|||
using Elsa.Workflows.Attributes; |
|||
using Elsa.Workflows.Models; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Collections; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BlobStoring; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
|||
|
|||
[Activity("Elsa", "BlobStoring", "Save a blob.", Kind = ActivityKind.Task)] |
|||
public class SaveBlob : BlobActivity |
|||
{ |
|||
[Input(Description = "Blob exists whether overwrite.")] |
|||
public Input<bool> Overwrite { get; set; } = null!; |
|||
|
|||
[Input(Description = "The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.")] |
|||
public Input<object> Data { get; set; } = null!; |
|||
|
|||
[Port] public IActivity? Error { get; set; } |
|||
|
|||
protected async override ValueTask ExecuteAsync(ActivityExecutionContext context) |
|||
{ |
|||
var cancellationToken = context.CancellationToken; |
|||
var data = await ResolveAsStreamAsync(Data.Get(context), cancellationToken); |
|||
var overwrite = Overwrite.GetOrDefault(context); |
|||
var path = Path.Get(context); |
|||
var blobContainer = GetBlobContainer(context); |
|||
|
|||
var logger = context.GetRequiredService<ILogger<SaveBlob>>(); |
|||
try |
|||
{ |
|||
await blobContainer.SaveAsync(path, data, overwrite, cancellationToken); |
|||
await context.CompleteActivityAsync(); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
logger.LogWarning(e, "Error while save a blob."); |
|||
context.AddExecutionLogEntry("Error", e.Message, payload: new |
|||
{ |
|||
e.StackTrace |
|||
}); |
|||
await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); |
|||
} |
|||
} |
|||
|
|||
private async Task<Stream> ResolveAsStreamAsync(object data, CancellationToken cancellationToken) |
|||
{ |
|||
if (data is Stream stream) |
|||
return stream; |
|||
|
|||
if (data is byte[] bytes) |
|||
return new MemoryStream(bytes); |
|||
|
|||
if (data is IFormFile formFile) |
|||
return formFile.OpenReadStream(); |
|||
|
|||
if (data is string stringData) |
|||
return new MemoryStream(Encoding.UTF8.GetBytes(stringData)); |
|||
|
|||
if (data is IEnumerable enumerable) |
|||
{ |
|||
var files = enumerable.Cast<object>().ToList(); |
|||
return files.Count == 1 ? await ResolveAsStreamAsync(files[0], cancellationToken) : await CreateZipArchiveAsync(files, cancellationToken); |
|||
} |
|||
|
|||
throw new NotSupportedException($"The provided data type is not supported: {data.GetType().Name}"); |
|||
} |
|||
|
|||
private async Task<Stream> CreateZipArchiveAsync(IEnumerable files, CancellationToken cancellationToken = default) |
|||
{ |
|||
var currentFileIndex = 0; |
|||
var zipStream = new MemoryStream(); |
|||
var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true); |
|||
|
|||
foreach (var file in files) |
|||
{ |
|||
var entryName = $"file-{currentFileIndex}.bin"; |
|||
var entry = zipArchive.CreateEntry(entryName); |
|||
var fileStream = await ResolveAsStreamAsync(file, cancellationToken); |
|||
await using var entryStream = entry.Open(); |
|||
await fileStream.CopyToAsync(entryStream, cancellationToken); |
|||
await entryStream.FlushAsync(cancellationToken); |
|||
entryStream.Close(); |
|||
currentFileIndex++; |
|||
} |
|||
|
|||
zipStream.Seek(0, SeekOrigin.Begin); |
|||
return zipStream; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.BlobStoring; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.BlobStoring; |
|||
|
|||
[BlobContainerName("workflow")] |
|||
public class ElsaBlobContainer |
|||
{ |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Elsa.Features.Services; |
|||
using LINGYUN.Abp.ElsaNext.BlobStoring.Features; |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Extensions; |
|||
|
|||
public static class ModuleExtensions |
|||
{ |
|||
public static IModule UseAbpBlobStoring(this IModule configuration, Action<BlobStoringFeature>? configure = null) |
|||
{ |
|||
configuration.Configure(configure); |
|||
return configuration; |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using Elsa.Extensions; |
|||
using Elsa.Features.Abstractions; |
|||
using Elsa.Features.Services; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Features; |
|||
|
|||
public class BlobStoringFeature : FeatureBase |
|||
{ |
|||
public BlobStoringFeature(IModule module) : base(module) |
|||
{ |
|||
} |
|||
|
|||
public override void Configure() |
|||
{ |
|||
Module.AddActivitiesFrom<BlobStoringFeature>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Path of the blob.": "Path of the blob.", |
|||
"Blob exists whether overwrite.": "Blob exists whether overwrite.", |
|||
"The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.": "The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.", |
|||
"BlobStoring": "Blob Storing", |
|||
"Delete Blob": "Delete a blob.", |
|||
"Read Blob.": "Read a blob.", |
|||
"Save Blob.": "Save a blob." |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Path of the blob.": "文件存储路径。", |
|||
"Blob exists whether overwrite.": "覆盖已有文件。", |
|||
"The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.": "要保存的文件数据。可以是流、二进制数据、字符串、表单文件或一组文件。", |
|||
"BlobStoring": "对象存储", |
|||
"Delete Blob": "删除文件", |
|||
"Read Blob": "打开文件", |
|||
"Save Blob": "保存文件" |
|||
} |
|||
} |
|||
Loading…
Reference in new issue