Расширения для работы с файлами в задании
Расширения для работы с файлами в задании¶
По умолчанию в задании не сохраняются прикрепленные файлы, но расширения позволяют реализовать их сохранение в основной карточке, либо в ее файловом сателлите. Пример последнего можно увидеть в расширениях типового решения TaskFilesExampleGetExtension
, TaskFilesExampleStoreExtension
и TaskEnableAttachFilesExampleUIExtension
для desktop- и web-клиента.
Создайте серверное расширение, которое при сохранении карточки перенесет файлы, относящиеся к заданиям, из файлового контейнера основной карточки в файловый сателлит:
using System.Threading.Tasks;
using Tessa.Cards;
using Tessa.Cards.Extensions;
using Tessa.Extensions.Platform.Server.Cards;
using Tessa.Platform;
namespace Tessa.Extensions.Default.Server.Cards
{
/// <summary>
/// Пример расширения, которое сохраняет файлы из заданий карточки в карточку - файловый сателлит.
/// </summary>
public class TaskFilesExampleStoreExtension : CardStoreExtension
{
private ICardRepository cardRepository;
public TaskFilesExampleStoreExtension(ICardRepository cardRepository)
{
Check.ArgumentNotNull(cardRepository, nameof(cardRepository));
this.cardRepository = cardRepository;
}
public override async Task BeforeRequest(ICardStoreExtensionContext context)
{
// Получаем карточку из запроса.
var card = context.Request.Card;
if (card.TypeID == CardHelper.FileSatelliteTypeID)
{
return;
}
Card fileSatellite = null;
// Перебираем файлы в файловом контейнере для того, чтобы сохранить те, что относятся к заданиям, в файловый сателлит.
for (int i = card.Files.Count - 1; i >= 0; i--)
{
var file = card.Files[i];
// Если у файла не определен TaskID, переходим к следующему.
if (file.TaskID is null)
{
continue;
}
// Если до сих пор не извлечен файловый сателлит для главной карточки, извлекаем его.
if (fileSatellite is null)
{
fileSatellite = await FileSatelliteHelper.GetFileSatelliteAsync(
context,
this.cardRepository);
// Если мы не можем получить файловый сателлит, то выходим из метода.
if (fileSatellite is null)
{
return;
}
context.Request.ForceTransaction = true;
}
// Добавляем файл в файловый сателлит.
fileSatellite.Files.Add(file);
// Настраиваем маппинг сохранения контента файла в карточку файлового сателлита
if (file.State != CardFileState.Deleted)
{
var fileMapping = context.Request.FileMapping.Add();
fileMapping.CardID = fileSatellite.ID;
fileMapping.FileID = file.RowID;
fileMapping.VersionRowID = file.VersionRowID;
fileMapping.SourceFileID = file.RowID;
fileMapping.StoreSource = file.StoreSource;
}
// Из главной карточки удаляем информацию по файлу.
card.Files.RemoveAt(i);
}
}
public override Task BeforeCommitTransaction(ICardStoreExtensionContext context)
{
if (context.ValidationResult.IsSuccessful())
{
// Сохраняем файловый сателлит.
return FileSatelliteHelper.StoreFileSatelliteAsync(context, this.cardRepository);
}
return Task.CompletedTask;
}
}
}
Зарегистрируйте серверное расширение на сохранение карточки следующим образом:
[Registrator]
public sealed class Registrator : RegistratorBase
{
public override void RegisterUnity()
{
this.UnityContainer
.RegisterType<TaskFilesExampleStoreExtension>(new ContainerControlledLifetimeManager())
;
}
public override void RegisterExtensions(IExtensionContainer extensionContainer)
{
extensionContainer
.RegisterExtension<ICardStoreExtension, TaskFilesExampleStoreExtension>(x => x
.WithOrder(ExtensionStage.AfterPlatform, 1)
.WithUnity(this.UnityContainer))
;
}
}
Important
Данное расширение должно быть зарегистрировано с порядком AfterPlatform
, т.к. обязательно должно выполняться после платформенного расширения, определяющего место хранения содержимого файла по умолчанию.
При чтении карточки с заданиями с сервера необходимо переносить файлы из файлового сателлита в карточки заданий, чтобы они стали доступны пользователю. Этой цели послужит серверное расширение, которое будет запускаться каждый раз при чтении карточки:
using System.Linq;
using System.Threading.Tasks;
using Tessa.Cards;
using Tessa.Cards.Extensions;
using Tessa.Extensions.Platform.Server.Cards;
using Tessa.Platform;
using Tessa.Platform.Collections;
namespace Tessa.Extensions.Default.Server.Cards
{
/// <summary>
/// Пример расширения, которое переносит файлы из карточки - файлового сателлита в карточки задач.
/// </summary>
public class TaskFilesExampleGetExtension : CardGetExtension
{
#region Fields
private ICardRepository cardRepository;
#endregion
#region Constructors
public TaskFilesExampleGetExtension(ICardRepository cardRepository)
{
Check.ArgumentNotNull(cardRepository, nameof(cardRepository));
this.cardRepository = cardRepository;
}
#endregion
#region Base Overrides
/// <inheritdoc/>
public override async Task AfterRequest(ICardGetExtensionContext context)
{
// Получаем карточку из ответа.
var card = context.Response.Card;
// Если в карточке нет задач, выходим из метода.
if (!card.Tasks.Any())
{
return;
}
// Извлекаем файловый сателлит для главной карточки.
var fileSatellite = await FileSatelliteHelper.GetFileSatelliteAsync
(this.cardRepository, card.ID, context.ValidationResult, false);
// Если мы не можем получить файловый сателлит, то выходим из метода.
if (fileSatellite is null)
{
return;
}
// Получаем и затем перебираем список файлов, что относятся к задачам.
var taskFiles = fileSatellite.Files;
foreach (var taskFile in taskFiles)
{
if (taskFile.TaskID is null
|| !card.Tasks.TryFirst(x => x.RowID == taskFile.TaskID, out var task))
{
continue;
}
// Добавляем в файловый контейнер файл, который уже в UI-расширении будет перенесен в файловый контейнер задачи.
var addedTaskFile = task.Card.Files.Add(taskFile);
// Добавляем информацию о версиях файла.
var response = await this.cardRepository.GetFileVersionsAsync(new CardGetFileVersionsRequest()
{
FileID = addedTaskFile.RowID,
CardID = fileSatellite.ID,
FileName = addedTaskFile.Name
}, context.CancellationToken);
addedTaskFile.Versions.Clear();
addedTaskFile.Versions.AddRange(response.FileVersions);
// В информации о файле указываем в качестве внешнего источника файловый сателлит.
addedTaskFile.ExternalSource = new CardFileContentSource
{
CardID = fileSatellite.ID,
FileID = addedTaskFile.RowID,
VersionRowID = addedTaskFile.VersionRowID,
Source = addedTaskFile.StoreSource,
CardTypeID = CardHelper.FileSatelliteTypeID
};
// Для карточки задачи, к которой прикреплен файл, выставим Version > 0.
// Это необходимо для того, чтобы изменить способ сохранения карточки с CardStoreMode.Insert на CardStoreMode.Update,
// и таким образом обеспечить работу логики, извлекающей по запросу список версий для прикрепленных файлов.
if (task.Card.Version == 0)
{
task.Card.Version = 1;
}
}
}
#endregion
}
}
Зарегистрируйте серверное расширение на загрузку карточки следующим образом:
[Registrator]
public sealed class Registrator : RegistratorBase
{
public override void RegisterUnity()
{
this.UnityContainer
.RegisterType<TaskFilesExampleGetExtension>(new ContainerControlledLifetimeManager())
;
}
public override void RegisterExtensions(IExtensionContainer extensionContainer)
{
extensionContainer
.RegisterExtension<ICardGetExtension, TaskFilesExampleGetExtension>(x => x
.WithOrder(ExtensionStage.BeforePlatform, 1)
.WithUnity(this.UnityContainer))
;
}
}
Теперь создайте UI-расширение для desktop-клиента, которое вызывается на все запросы сохранения карточки.
Данное расширение на клиентской стороне при сохранении карточки переносит файлы из заданий в файловый контейнер основной карточки:
using System.Linq;
using System.Threading.Tasks;
using Tessa.Cards;
using Tessa.Files;
using Tessa.UI.Cards;
using Tessa.UI.Cards.Forms;
using Tessa.UI.Cards.Tasks;
namespace Tessa.Extensions.Default.Client.UI
{
/// <summary>
/// Пример расширения UI, которое позволяет добавлять файлы в задачи карточки.
/// </summary>
public sealed class TaskEnableAttachFilesExampleUIExtension : CardUIExtension
{
#region Base Overrides
///<inheritdoc/>
public override async Task Saving(ICardUIExtensionContext context)
{
// Если в форме с карточкой нет задач, то ничего не делаем.
if (context.Model.MainForm is not DefaultFormTabWithTasksViewModel mainForm || !mainForm.Tasks.Any())
{
return;
}
// Перебираем файлы в карточках задач главной формы.
foreach (var taskViewModel in mainForm.Tasks.OfType<TaskViewModel>())
{
await taskViewModel.TaskModel.FileContainer.Files.EnsureAllContentModifiedAsync(context.CancellationToken).ConfigureAwait(false);
foreach (var fileCard in taskViewModel.TaskModel.CardTask.Card.Files)
{
// Отправляем в контейнер только измененные файлы, либо те, у которых была добавлена или снята электронная подпись.
if (fileCard.State != CardFileState.None ||
fileCard.Flags != CardFileFlags.None ||
(fileCard.Card.Sections.TryGetValue(CardSignatureHelper.SectionName, out CardSection signatures) &&
signatures.Rows.Any(x => x.State != CardRowState.None)))
{
if (taskViewModel.TaskModel.FileContainer.TryGetFile(fileCard.RowID) is { } file)
{
// Добавляем файл в контейнер главной карточки, если он есть.
context.FileContainer.Files.Add(file);
}
// Добавляем файл в главную карточку.
var newFileCard = context.Card.Files.Add(fileCard);
// Записываем в поле TaskID идентификатор текущей задачи.
newFileCard.TaskID = taskViewModel.TaskModel.CardTask.Card.ID;
}
}
}
}
#endregion
}
}
Зарегистрируйте UI-расширение следующим образом:
[Registrator]
public sealed class Registrator : RegistratorBase
{
public override void RegisterExtensions(IExtensionContainer extensionContainer)
{
extensionContainer
.RegisterExtension<ICardUIExtension, TaskEnableAttachFilesExampleUIExtension>(x => x
.WithOrder(ExtensionStage.BeforePlatform, 1)
.WithSingleton())
;
}
}
Для web-клиента потребуется создать и зарегистрировать собственное расширение:
import { ICardUIExtensionContext, CardUIExtension } from 'tessa/ui/cards';
import { DefaultFormTabWithTasksViewModel } from 'tessa/ui/cards/forms';
export class TaskEnableAttachFilesExampleUIExtension extends CardUIExtension {
public async saving(context: ICardUIExtensionContext): Promise<void> {
// Если в форме с карточкой нет задач, то ничего не делаем.
if (
!(context.model.mainForm instanceof DefaultFormTabWithTasksViewModel) ||
!context.model.mainForm.tasks.length
) {
return;
}
const form = context.model.mainForm;
// Перебираем файлы в карточках задач главной формы.
for await (const task of form.tasks) {
await task.taskModel.fileContainer.ensureAllContentModified();
// Отправляем в контейнер только измененные файлы, либо те, у которых была добавлена или снята электронная подпись.
const filesCount = task.taskModel.card.files.length;
for (let i = 0; i < filesCount; i++) {
const cardFile = task.taskModel.card.files[i];
if (cardFile.hasChanges()) {
cardFile.taskId = task.id;
context.storeRequest.card.files.add(cardFile);
const file = task.taskModel.fileContainer.files.find(x => x.id === cardFile.rowId)!;
if (file) {
await file.lastVersion.ensureContentDownloaded();
await context.fileContainer.addFile(file);
}
}
}
}
}
}
Регистрация производится следующим образом:
import { ExtensionContainer, ExtensionStage } from 'tessa/extensions';
import { TaskEnableAttachFilesExampleUIExtension } from './taskEnableAttachFilesExampleUIExtension';
ExtensionContainer.instance.registerExtension({
extension: TaskEnableAttachFilesExampleUIExtension,
singleton: true,
stage: ExtensionStage.BeforePlatform
});